Getting to Know the Ruby Standard Library – MiniTest
Ghetto or not, if you want to use or contribute to the ruby standard library, getting to know the source is essential. We will look at one of the libraries everyone should be acquainted with, test/unit. There are dozens of testing frameworks in ruby, but this is the one you know everyone will have available. I’ll assume you’re using ruby 1.9.x, because it’s awesome.
Overview
Test/Unit is a ruby implementation of original
xUnit architecture. In general you write your tests by extending
Test::Unit::TestCase
, adding methods that look like
test_*something*
, and then you can run that file directly with
ruby my_neat_test.rb
. Since it’s best to have a purpose when learning, we’ll try to answer two questions:
- How does ruby know it should run the tests when you execute your file?
- How does test unit know which methods are tests?
Test/Unit
To get started, open up Test/Unit (qw test
if you have
Qwandry installed).
Looking at
unit.rb
we immediately see something odd:
# test/unit compatibility layer using minitest.
require 'minitest/unit'
require 'test/unit/assertions'
require 'test/unit/testcase'
In ruby 1.9
minitest was swapped in to replace test/unit. The code in
test/unit
is just here to make sure all the tests behave the same way in ruby 1.9 as they did in ruby 1.8. If you scroll down to the bottom of
unit.rb
, you will find this:
MiniTest::Unit.autorun
This is going to get called any time that you require
test/unit
, so lets find out what it does, and I suspect we can unravel our first question.
Minitest
There isn’t much in
test/unit
so lets take a look at
minitest
since that’s what ruby 1.9.x is really using (qw minitest
). Since we saw that
autorun
is called whenever we require
test/unit
,
autorun.rb
sounds like a good place to start looking. The contents of this file?
MiniTest::Unit.autorun
Ok, well we have three more files left, so
unit.rb
is probably another good place to look. A quick search for
autorun
will yield:
class Unit
#...
def self.autorun
at_exit {
next if $! # don't run if there was an exception
exit_code = MiniTest::Unit.new.run(ARGV)
exit false if exit_code && exit_code != 0
} unless @@installed_at_exit
@@installed_at_exit = true
end
We see that
at_exit
is being called. If you haven’t run across this before, so a quick peak at the
docs for ruby-core shows that this block will be executed right before ruby exits. Notice that minitest calls
MiniTest::Unit.new.run(ARGV)
inside the block? Now you know how the tests get run. You can also see that
@@installed_at_exit
is in there to prevent your tests from being launched more than once. Neat stuff, one question down.
Next up, how does it know which tests to run? We saw that
MiniTest::Unit.new.run(ARGV)
is going to get called, so that’s a good place to start.
def run args = []
@verbose = args.delete('-v')
filter = if args.first =~ /^(-n|--name)$/ then
args.shift
arg = args.shift
arg =~ /\/(.*)\// ? Regexp.new($1) : arg
else
/./ # anything - ^test_ already filtered by #tests
end
#...
We can see that this code starts out by using the options from
ARGV
to configure
MiniTest::Unit
. Let’s look at
filter
it will either end up being the name of a test, or a regexp that matches everything. The comment looks promising too. Hopping down a few lines we see that filter gets used:
run_test_suites filter
Looking ahead we see that
TestCase.test_suites
seems to be enumerable, and each suite has a set of test methods.
def run_test_suites filter = /./
#...
TestCase.test_suites.each do |suite|
suite.test_methods.grep(filter).each do |test|
inst = suite.new test
#...
We’ll ignore the
test_suites
for now and look into the
test_methods
code, this is probably where minitest finds all of the tests you wrote.
def self.test_methods
methods = public_instance_methods(true).grep(/^test/).map { |m|
m.to_s
}.sort
#...
And there we go, your test inherits from
TestCase
and
TestCase.test_methods
introspects on itself looking for all the public methods that start with the word
test
. So now we’ve answered our questions, and if you were paying attention, we learned a few things that might be useful.
Recap
In ruby 1.9 test/unit was replaced with minitest. It turns out that minitest registers an
at_exit
hook, and that it introspects on its own methods to figure out what to run. Now while you were in the code, perhaps you noticed few other interesting things that might be of use later:
- minitest defines
skip
which lets you skip a test and give a reason - There is a mocking library available in
mock.rb
- There is a tiny mocking library available in
mock.rb
- There is a small BDD testing library available in
spec.rb
Now aren’t you glad you took a moment to look at the standard library? Go forth and do something great.
More articles in this series
- Getting to Know the Ruby Standard Library – Delegator
- Getting to Know the Ruby Standard Library – WeakRef
- Getting to Know the Ruby Standard Library – Timeout
- Getting to Know the Ruby Standard Library – Pathname
- Getting to Know the Ruby Standard Library – Abbrev
- Getting to Know the Ruby Standard Library – TSort
- Getting to Know the Ruby Standard Library – MiniTest::Mock
- Getting to Know the Ruby Standard Library – Shellwords
- Getting to Know the Ruby Standard Library – MiniTest