Getting to Know the Ruby Standard Library – MiniTest

This article has been republished on Monkey and Crow.

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:

  1. How does ruby know it should run the tests when you execute your file?
  2. 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.

5 Comments

Filed under ruby, stdlib

5 responses to “Getting to Know the Ruby Standard Library – MiniTest

  1. Pingback: Getting to Know the Ruby Standard Library – Shellwords | End of Line

  2. Pingback: Getting to Know the Ruby Standard Library – MiniTest::Mock | End of Line