Note: This is the second and final installment of the RSpec — Getting Started series. If you haven’t read the first part, please go back (link) and read it before continuing.
In the previous edition of “Getting Started with RSpec” we made a simple calculator with unit tests. We saw how tests can help us maintain code even when working with pretty dumb programmers.
In this installment we are going to be focusing on Test Driven Development (TDD). What is TTD?
Test Driven Development is the workflow of writing a test, letting it fail, and then changing your code to make it (and all the previous tests) pass.
Enough talk, let’s get coding
To demonstrate let’s add a subtract
method to our calculator. To do this following a TDD workflow, we first add a test with our expectations:
# File: spec/calculator_spec.rbrequire './calculator.rb'describe "calculator" doit 'adds numbers' do
...
endit 'subtracts numbers' do
calc = Calculator.new
expect(calc.subract(5,1)).to eql(4)
endend
This new subtracts numbers
test is expecting that when we call the subtract
function and pass it two parameters, 5
and 1
we will be returned a value of 4
Let’s run the tests to make sure we don’t have any syntax errors:
calculator $ bundle exec rspec
.FFailures:1) calculator subtracts numbers
Failure/Error: expect(calc.subtract(5,1)).to eql(4)
NoMethodError:
undefined method `subtract' for #<Calculator:0x007f9dc4a65f40>
# ./spec/calculator_spec.rb:11:in `block (2 levels) in <top (required)>'Finished in 0.00236 seconds (files took 0.08835 seconds to load)
2 examples, 1 failureFailed examples:rspec ./spec/calculator_spec.rb:9 # calculator subtracts numbers
Just what we expected. One of our tests has failed. We can see which test has failed in the last line of the output.
Now that we have written our test and have seen it fail, it’s time to make it pass.
Let’s create a subtract method in our Calculator:
# File: calculator.rbclass Calculator
def add(x, y)
x + y
end
def subtract(x, y)
x - y
end
end
The last step in TDD is to make all the tests pass. This is sometimes called going “Green,” since the output of passing tests is green. To see if our Calculator behaves as we want it to, run the tests:
calculator $ rspec
..Finished in 0.00224 seconds (files took 0.08993 seconds to load)
2 examples, 0 failures
Perfect! You’ve just completed your first TDD cycle! That wasn’t too difficult, was it?
Now that you’re feeling confidant in your abilities, let’s repeat this process for themultiply
and divide
functions. Let’s start with multiply
:
# File: spec/calculator_spec.rbrequire './calculator.rb'describe 'calculator' do
it 'adds numbers' do
...
endit 'subtracts numbers' do
...
endit 'multiplies numbers' do
calc = Calculator.new
expect(calc.multiply(3,4)).to eql(12)
endend
Make sure your tests fail by running rspec
Then change your code so your tests pass:
# File: calculator.rbclass Calculator
def add(x, y)
x + y
enddef subtract(x, y)
x - y
enddef multiply(x, y)
x*y
endend
Do your tests now pass?
calculator $ rspec
...Finished in 0.00262 seconds (files took 0.08638 seconds to load)
3 examples, 0 failures
Lookin’ good! Lastly let’s finish with our divide
function:
# File: spec/calculator_spec.rbrequire './calculator.rb'describe 'calculator' do
it 'adds numbers' do
...
endit 'subtracts numbers' do
...
endit 'multiplies numbers' do
...
endit 'divides numbers' do
calc = Calculator.new
expect(calc.divide(5,2)).to eql(2.5)
endend
Watch your test fail and then add the divide
method:
# File: calculator.rbclass Calculator
def add(x, y)
x + y
enddef subtract(x, y)
x - y
enddef multiply(x, y)
x*y
enddef divide(x, y)
x / y
end
end
One final check gives us:
calculator $ rspec
...FFailures:1) calculator divides numbers
Failure/Error: expect(calc.divide(5,2)).to eql(2.5)
NoMethodError:
undefined method `divide' for #<Calculator:0x007fc9c0345440>
# ./spec/calculator_spec.rb:23:in `block (2 levels) in <top (required)>'Finished in 0.00278 seconds (files took 0.09446 seconds to load)
4 examples, 1 failureFailed examples:rspec ./spec/calculator_spec.rb:21 # calculator divides numbers
Whoops! That’s clearly not right. We were expecting all our tests to pass. What’s going on here?
To get more information about the error, we can add a handy feature of RSpec, output formatting. In the console run the command rspec --format documentation
and you should get this:
calculator $ rspec
...FFailures:1) calculator divides numbers
Failure/Error: expect(calc.divide(5,2)).to eql(2.5)
expected: 2.5
got: 2
(compared using eql?)
# ./spec/calculator_spec.rb:23:in `block (2 levels) in <top (required)>'Finished in 0.01352 seconds (files took 0.09081 seconds to load)
4 examples, 1 failureFailed examples:rspec ./spec/calculator_spec.rb:21 # calculator divides numbers
We were expecting the value 2.5
to be returned, but instead we received a value of 2
This is where TDD really shines bright. If we hadn’t had these tests in place, we would have been in trouble when we needed to use the divide function in production.
To fix this problem, we need to change one the divde
inputs to Float
with the .to_f
function:
def divide(x, y)
x / y.to_f
end
Now that we’ve made a change, we rerun the tests:
calculator $ rspec
....Finished in 0.00271 seconds (files took 0.09715 seconds to load)
4 examples, 0 failures
That’s better. Now that all of our tests are passing, we can call it a day. Great work!
Test Driven Development is an important tool in any developer’s toolbox. It allows us to develop with the confidence that our code is not going to break if we change something. It’s especially helpful when refactoring our code to ensure that all the functions work properly. Refactoring is almost impossible without test to back it up. If you have any questions about TDD please feel free to leave a comment below