Make Your Ruby Tests Cleaner with Minitest and Shoulda
Back before RSpec and Cucumber, a core library in Ruby called Test Unit existed. It was simply a framework for unit testing code. Ever since Ruby 1.9.3, however, Test Unit has been replaced by Minitest as the included testing framework for Ruby. In this article, we’re going to overview Minitest (as it has now superceded Test Unit) and the benefits of shoulda-context, a Test::Unit
/Minitest
framework.
Prerequisites
- Ruby (duh; current 2.1.2 would be best)
- The ability to run
gem install GEM
without a hassle. - A small amount of TDD knowledge, with a sprinkle of BDD.
Test driven development (TDD) has always been a fixture in the Ruby community. It is a core concept in extreme programming and is used in virtually every Ruby project. Making unit tests with Minitest is simple, but first, we need a Ruby library to test. Our final project structure will look like this:
.
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── lib
│ └── calculator.rb
└── test
├── test_calculator.rb
├── test_calculator_basic.rb
└── test_helper.rb
You can find the repo for this project here.
Our library is a calculator with two methods: add
and subtract
.
class Calculator
def add(num1, num2)
num1 + num2
end
def subtract(num1, num2)
num1 - num2
end
end
To test this library with Minitest, create a test class that inherits from Minitest::Test
.
# file: test/test_calculator_basic.rb
require 'minitest/autorun'
require 'calculator' # don't worry, this works
class TestCalculator < Minitest::Test
end
Tests inside of this class are methods that start with test_
and should describe what the test is testing.
# file: test/test_calculator_basic.rb
require 'minitest/autorun'
require 'calculator'
class TestCalculator < Minitest::Test
def setup
@calc = Calculator.new
end
def test_proper_addition
assert_equal 4, @calc.add(2, 2)
end
def test_proper_subtraction
assert_equal 0, @calc.subtract(2, 2)
end
end
To run this test, use the following command: $ ruby -I test:lib test/test_calculator_basic.rb
. This may seem a little repetetive, especially if you plan on using a CI server, so we can use a simple Rake task for automation.
# file: Rakefile
require 'rake/testtask'
Rake::TestTask.new do |task|
task.libs << %w(test lib)
task.pattern = 'test/test_*.rb'
end
task :default => :test
Now, the tests run by simply calling rake
or rake test
. That should give us the following output:
Run options: --seed 41508
# Running:
..
Finished in 0.001047s, 1910.2197 runs/s, 1910.2197 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
As you can see, our tests passed. Let’s add some more…
# file: lib/test_calculator_basic.rb
require 'minitest/autorun'
require 'calculator'
class TestCalculator < Minitest::Test
def setup
@calc = Calculator.new
end
# add
def test_add
assert_equal 4, @calc.add(2, 2)
end
def test_failing_add
refute_equal 5, @calc.add(2, 2)
end
# subtract
def test_subtract
assert_equal 0, @calc.subtract(2, 2)
end
def test_failing_subtract
refute_equal -1, @calc.subtract(2, 2)
end
end
The results of this are similar to the last ones.
Run options: --seed 4571
# Running:
....
Finished in 0.001081s, 3700.2775 runs/s, 3700.2775 assertions/s.
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
You may have noticed that the test methods are starting to get more confusing to read. This is because you’re limited by_underscored_names_as_tests
. This is where shoulda-context comes in. The good folks over at thoughtbot created this library to help give context to unit tests. Here is the above test written in shoulda-context format:
# file: lib/test_calculator.rb
require 'minitest/autorun'
require 'shoulda/context'
require 'calculator'
class TestCalculator < Minitest::Test
context 'a calculator' do
setup do # notice the difference
@calc = Calculator.new
end
should 'add two numbers properly' do
assert_equal 4, @calc.add(2, 2)
end
should 'not add incorrectly' do
refute_equal 5, @calc.add(2, 2)
end
should 'subtract two numbers properly' do
assert_equal 0, @calc.subtract(2, 2)
end
should 'not subtract incorrectly' do
refute_equal -1, @calc.subtract(2, 2)
end
end
end
If you haven’t yet, run gem install shoulda-context
in your terminal to install shoulda-context.
As you can see, the tests are much more readable. A human can read the tests like this: context: should 'do something'
. For example, the first test can be read as: a calculator should add two numbers properly
. This is much more expressive than the previous example. The tests now seem more Ruby-esque.
Expanding: Output
If you’ve been following along with the code examples, then you may notice that the test output isn’t as nice as RSpec or Cucumber’s output. To fix this, we can use a little gem called minitest-reporters.
First, we’re going to split up our tests a little more. If you have experience with Rails and testing, then you have probably used test_helper.rb
. We can do the same thing in our project by just creating a file in our tests directory called test_helper.rb
Inside of test_helper.rb
, we’re going to put the first two beginning require
statements that we have been using in our tests.
# file: test/test_helper.rb
require 'minitest/autorun'
require 'shoulda/context'
Notice that we did not include require 'calculator'
. This is done because we could expand our library at some point beyond just the Calculator
class. Since not everything that we test in our library will need to include calculator
, we do not include it in test_helper.rb
.
Our calculator tests file should now look something like this:
# file: test/test_calculator.rb
require 'test_helper'
require 'calculator'
# ... lots of tests ...
To setup minitest-reporters with our new setup, first install the gem (gem install minitest-reporters
). Now, open up test_helper.rb
again and add the following:
# file: test/test_helper.rb
require 'minitest/autorun'
require 'minitest/reporters' # requires the gem
require 'shoulda/context'
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # spec-like progress
If we run the tests (ruby -I test:lib test/test_calculator.rb
or rake test
with only one testing file), we should see:
Started
TestCalculator
test: a calculator should subtract two numbers properly. PASS (0.00s)
test: a calculator should not add incorrectly. PASS (0.00s)
test: a calculator should add two numbers properly. PASS (0.00s)
test: a calculator should not subtract incorrectly. PASS (0.00s)
Finished in 0.00141s
4 tests, 4 assertions, 0 failures, 0 errors, 0 skips
Much more readable and it has color! Notice how the format is the same as how the tests read to a human. This is how it should be.
Conclusion
Testing is not only recommended, but it is almost required when working on a Ruby project. Tools like shoulda-context are here to help ease that process. We covered Minitest basics, automated testing, shoulda-context, and fancy (colored, human-readable) output. If you’d like to learn more, check out these links:
- Minitest Library (included with Ruby)
- Minitest Repo (official repository)
- shoulda-context (shoulda-context GitHub page)
- minitest-reporters (minitest-reporters GitHub page)
- Code from This Article (all of the code from this article)
- Extreme Programming – Wikipedia (XP wikipedia page)
- Extreme Programming – Official (XP official website)
- DDH Offended by RSpec (old, but still relevant)