I am just trying to understand the basics behind unit testing. I wrote a Player class in a file called Player.rb. Here is the code:
Class Player
attr_reader :health
attr_accessor :name
def initialize(name, health=100)
#name = name.capitalize
#health = health
end
def blam
#health -= 10
puts "#{#name} just got blammed yo."
end
def w00t
#health += 15
puts "#{#name} just got w00ted."
end
def score
#health + #name.length
end
def name=(new_name)
#name = new_name.capitalize
end
def to_s
puts "I'm #{#name} with a health of #{#health} and a score of #{score}"
end
end
Here is my spec file:
require_relative 'Player'
describe Player do
it "has a capitalized name" do
player = Player.new("larry", 150)
player.name.should == "Larry"
end
end
Does that seem about right? My question is in regards to the syntax of the spec file. I understand why I need to require the Player class. But what is the describe and it sections of the code doing? Why do I need the it section? All it seems to be doing is defining a string right?
Finally, when I run rspec player_spec.rb from Terminal, I get this warning:
Deprecation Warnings:
Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` instead. Called from /Users/Jwan/studio_game/player_spec.rb:7:in `block (2 levels) in <top (required)>'.
What does the above warning mean? Do I have to replace should with enable syntax? How do I enable the :should syntax? Why is :should written as a symbol?
Yes, that seems about right. One thing you might find useful is to use subject, which lets you define a "subject" to use in multiple tests:
describe Player do
subject { Player.new("larry", 150) }
it "has a capitalized name" do
expect(subject.name).to eq "Larry"
end
end
This way you don't have to define player over and over again in every test—subject will automatically initialize it for you each time.
describe and it are primarily for organization. A large project will ultimately have thousands of tests, and a change in one class might cause a test to fail for a completely different part of application. Keeping tests organized makes it much easier to find and fix errors as they occur.
As for your warning, it looks like you're using an old guide or tutorial that tells you to use "should" syntax, but in RSpec 3 this syntax is deprecated and "expect" syntax is required instead. You can see how I changed your code above to use "expect" syntax. Here's a good blog post on the new syntax (and why the old syntax is deprecated).
it sets up an actual example. An example can be thought of as an actual test which contains one or more expectations (best practice says one expectation per example).
The expect method and matchers only exist in the it block. Variables set up with let and subject are unique for each example.
scenario is an alias for it used in feature (acceptance) specs.
describe, context and feature are used to group examples together. This provides both readability and encapsulation for let and subject variables.
Passing a class to describe also sets up an implicit subject:
RSpec.describe Array do
describe "when first created" do
it { is_expected.to be_empty }
end
end
RSpec has relativly recently undergone a large shift towards eliminating "monkey patching" core ruby classes which depreciated the should syntax.
While it is recommended to use expect for new projects, you can allow should and other monkey patching methods.
Related
I want to monkey patch a String#select method, and want to create a test suite that checks that it doesn't use Array#select.
I tried creating a bunch of tests using to_not receive, using both to_not receive(Array:select) and just to_not receive(:select). I also tried using an array (string.chars) instead of the string . Google and stack overflow did not bring an answer.
describe "String#select" do
it "should not use built-in Array#select" do
string = "HELLOworld".chars
expect(string).to_not receive(Array:select)
end
end
Expected: a working test suite that checks that Array#method has not been used in the whole method.
Actual output: I'm getting an error that not enough arguments have been used. Output log below:
1) RECAP EXERCISE 3 Proc Problems: String#select should not use built-in Array#select
Failure/Error: expect(string).to_not receive(Array:select)
ArgumentError:
wrong number of arguments (given 0, expected 1..4)
# ./spec/problems_spec.rb:166:in `select'
# ./spec/problems_spec.rb:166:in `block (4 levels) in <top (required)>'
First of all: tests are supposed to check the results of methods called, not the way they are implemented. Relying to much on this would get you in trouble.
But there might be a legit reasons to do it, but think hard of you can test it other way:
Let's say that String#select uses Array#select internally, and the latter is buggy under some circumstances. It's better to make a test, setting up the universe in a way that would trigger the bug and check that the buggy behavior is not present . Then patch the String#select and have the test green. It's much better approach, because the test now tells everyone why you're not supposed to use Array#select internally. And if the bug is removed, it's the easiest thing under the sun to remove patch and check if the spec is still green.
That being said, if you still need that you can use expect_any_instance_of to acomplish that, for example this spec would fail:
class String
def select(&block)
split.select(&block) # remove this to make the spec pass
end
end
specify do
expect_any_instance_of(Array).not_to receive(:select)
'foo'.select
end
If you don't want to use expect_any_instance_of (because reasons), you can temporarily overwrite a method in a class to fail:
class String
def select(&block)
#split.select(&block)
end
end
before do
class Array
alias :backup_select :select
def select(*)
raise 'No'
end
end
end
after do
class Array
alias :select :backup_select # bring the original implementation back
end
end
specify do
expect { 'foo'.select }.not_to raise_error
end
Aliasing is needed to bring back the original implementation, so you don't mess up the specs that are run after this one.
But you can see how involved and messy this approach is.
Anyway - what you're trying to achieve is most probably a design issue, but it's hard to tell without more details.
I'm brand new to RSpec and TDD. I was wondering if someone might help me with creating a test well-suited for this Module:
module Kernel
# define new 'puts' which which appends "This will be appended!" to all puts output
def puts_with_append *args
puts_without_append args.map{|a| a + "This will be appended!"}
end
# back up name of old puts
alias_method :puts_without_append, :puts
# now set our version as new puts
alias_method :puts, :puts_with_append
end
I'd like for my test to check that the content from a 'puts' ends with "This will be appended!". Would that be a sufficient test? How would I do that?
The best tests test what you're trying to achieve, not how you achieve it... Tying tests to implementation makes your tests brittle.
So, what you're trying to achieve with this method is a change to "puts" whenever your extension is loaded. Testing the method puts_with_append doesn't achieve this goal... If you later accidentally re-alias that to something else, your desired puts change won't work.
However, testing this without using an implementation detail would be rather difficult, so instead, we can try to push the implementation details down to somewhere they won't change, like STDOUT.
Just the Test Content
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
Full Test
I'm going to turn this into a blog post within the next day or so, but I think you should also consider that you've got a desired result for one and many arguments, and your tests should be easy to read. The ultimate structure I'd use is:
require "rspec"
require "./your_extention.rb"
describe Kernel do
describe "#puts (overridden)" do
context "with one argument" do
it "should append the appropriate string" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
end
end
context "with more then one argument" do
it "should append the appropriate string to every arg" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
$stdout.should_receive(:write).with("TwoThis will be appended!")
puts("One", "Two")
end
end
end
end
So Module can be used in Ruby to provide namespacing in addition to mixins, as so:
module SomeNamespace
class Animal
end
end
animal = SomeNamespace::Animal.new
But I've also seen the following used:
module SomeNamespace
end
class SomeNamespace::Animal
end
animal = SomeNamespace::Animal.new
My question is how they're different (if they are) and which is more idiomatic Ruby?
The difference lies in nesting.
In the example below, you can see that the former method using class Foo, can get the outer scope's constant variables BAR_A without errors.
Meanwhile, class Baz will bomb with an error of uninitialized constant A::B::Baz::BAR_A. As it doesn't bring in A::* implicitly, only A::B::*explicitly.
module A
BAR_A = 'Bar A!'
module B
BAR_B = 'Bar B!'
class Foo
p BAR_A
p BAR_B
end
end
end
class A::B::Baz
p BAR_A
p BAR_B
end
Both behaviors have their place. There's no real consensus in the community in my opinion as to which is the One True Ruby Way (tm). I personally use the former, most of the time.
The only difference between the two approaches is that the second one will throw uninitialized constant Object::SomeNamespace if the namespace hasn't previously been declared.
When declared in a single file, I would opt for the first one because you don't have to repeat SomeNamespace.
When using multiple files I also use the second one, to avoid running into the following problem:
# in a.rb
require 'b'
module SomeNamespace
def self.animal
Animal.new
end
end
# in b.rb
class SomeNamespace::Animal
end
# irb
require 'a' # explodes with the uninitialized constant error
This example may be contrived, but it's easy to trigger it if your code base is a little bit bigger. I usually use the explicit way (your first one) to avoid this.
One thing that may be helpful when using the second form is that it will detect typos in the namespace.
There doesn't seem to be an established way to create namespaces, Devise for example mixes both approaches: first one, second one.
I created a program that tracks car mileage and service history in order to update the user for upcoming service needs for the car.
I have three classes: Car, CarHistory, and CarServiceHistoryEntry. The third one is straightforward; it holds all the attributes associated with a service: date, mileage, service performed, etc. The CarHistory class is as follows:
require_relative 'car_service_history_entry'
class CarHistory
attr_reader :entries
def initialize (*entry)
if entry.size > 1
#entries = []
else
#entries = entry
end
end
def add_service_entry entry
#entries << entry
end
def to_s
entries_string = ""
#entries.each {|entry| entries_string << "#{entry.to_s}\n"}
entries_string
end
end
In initialize, should the class of entry be checked?
In add_service_entry, adopting duck typing (as in Andy Thomas's argument in "Programming Ruby"), would I even test if a CarServiceHistoryEntry could be added? Couldn't I just pass a String instead of setting up and then adding CarServiceHistoryEntry in my unit testing?
Since the only necessary attributes of a CarHistory are the entries array and the to_s method, should I just scrap this class all together and put it into the car class?
For 1 and 2, you need to release your tight grip on "strict-typing" when you move to a loose-typed language like Ruby.
Should you check your input arguments ? The traditional answer would be yes. An alternative way would be to have good names and unit tests that document and specify how the type is supposed to work. If it works with other types, fine.. that's an added bonus. So if you pass in an incompatible type, it would blow up with an exception, which is good enough in most-cases. Try it out and see how it feels (possible outcomes: Liberating / "Retreat!". But give it a fair try.). Exceptions would be if you're designing public APIs for shared libraries - in which the rules are different. You need to fail fast and informatively for bad-input.
As for clubbing car_history into car - I'd ask what the responsibilities of your Car class are. If maintaining its own history is one of them, you could club them. In the future, if you find a lot of methods creeping in related to car history, you could again reverse this decision and extract the CarHistory type again. Use the SingleResponsibilityPrinciple to make an informed decision. This is just OOP - Ruby doesn't degrade object design.
Code Snippet: the code can be more concise
# just for simplicity, I'm making HistoryEntry a string, it could be a custom type too
class CarServiceHistoryEntry << String
end
class CarHistory
attr_reader :entries
def initialize(*history_entries)
#entries = history_entries
end
def add_service_entry(entry)
#entries << entry
end
def to_s
#entries.join("\n")
end
end
irb>x = CarHistory.new("May 01 Overhaul", "May 30 minor repairs")
irb>x.add_service_entry("June 12 Cracked windshield")
irb>x.to_s
=> "May 01 Overhaul\nMay 30 minor repairs\nJune 12 Cracked windshield"
It's hard to comment on the relationship of the CarHistory class to your others, but I'm sure it will become clear to you as you go along.
A couple of your methods could be simplified, although I must say I didn't understand the if in initialize, perhaps it was just backwards and should have been > 0.
def initialize *entry
#entries = entry # if not specified it will be [] anyway
end
def to_s
#entries.join "\n"
end
And yes, Ruby should be simple. You don't need to litter your code with runtime type checks. If the code runs your unit tests then you can just declare victory. The zillions of explicit conversions tend to patch up type errors anyway.
Ruby is going to check your types at run-time anyway. It's perfectly reasonable to leave the type checking to the interpreter and put your effort into functional tests.
I'll skip the first two questions and answer the third. If the only attribute of a CarServiceHistoryEntry is a string, then yes, scrap CarHistory (as well as CarServiceHistoryEntry) and add a service_history attribute to Car which would just be an array of strings. Until proven otherwise, simpler is better.
As to duck typing, you would never want to test if something 'is a' only see if it 'responds to' (at most).
Finally, to answer question #1, no its supposed to be even simpler :)
Hope this helps,
Brian
I have a number of ruby files (a.rb, b.rb, c.rb) which define very similar classes. (They should test the same)
I've written a unit test to test these subclasses and I've generated contexts for each of the classes programatically (see below) — should I instead programatically create entire Test Classes instead? If so why and how?
I'm using the shoulda unit test extensions so my files look something like this:
a.rb
class ItsA
def number
1123
end
end
b.rb
class ItsB
def number
6784
end
end
test_letters.rb
require 'rubygems'
require 'test/unit'
require 'shoulda'
class LettersTest < Test::Unit::TestCase
Dir.glob('letters/*.rb') do |letter|
context "The #{letter} letter file"
setup do
# Here I require the ruby file and allocate
# #theclass to be an instance of the class in the file.
# I'm actually testing JavaScript using Harmony, but
# putting those details in might complicate my question.
end
should "return a number" do
assert #theclass.number.is_a? Number
end
end
end
This does the job reasonably well, but should I do some other jiggerypokery and create LetterATest, LetterBTest etc. automatically instead? If so, how would you go about doing it and why?
This really depends on how similar your classes are, but assuming they're pretty much identical and require a few small tests, and you're using shoulda, you could do something like:
class LettersTest < Test::Unit::TestCase
context "The letter classes"
setup do
#instances = # your code to get a list of the instances
end
should "return a number" do
#instances.each do |instance|
assert instance.number.is_a?(Number), "#{instance.class}.number should be a number"
end
end
end
end
In our codebase we've found that a large number of auto-generated contexts and tests leads to pretty slow test execution, so we favor the approach above to minimize the number of contexts/tests. You may not have this issue.