Despite RSpec's awesomeness, Test::Unit remains the most popular Ruby testing tool out there outside of Rails apps. I've recently been code walking through a lot of Ruby libraries for my Ruby Reloaded course and the typical arrangement is Test::Unit, sometimes coupled with Shoulda or Contest for some extra syntactic sweetness.
Part of the reason for Test::Unit's enduring popularity is its presence in the Ruby standard library but, also, its general 'lightness' and speed. When you're writing a large app, using a powerful full-featured system like RSpec has significant benefits (particularly stakeholder involvement in writing the specs). But when you're working on a library that might spread far and wide and is aimed solely at developers, the pros of Test::Unit shine through.
Enter MiniTestWith Ruby 1.9, however, MiniTest entered the standard library. require 'test/unit' still works in Ruby 1.9 but it's provided through a compatibility layer on top of MiniTest, so if you're using require 'test/unit' in Ruby 1.9, you're really using MiniTest under the hood. It's possible to switch to using MiniTest::Unit directly without much effort (a few changed assertions and minor additions) but more exciting is the inclusion of MiniTest::Spec, a contextual RSpec-esque syntax, ready to run out of the box with Ruby 1.9.
Note: Ruby 1.8 users can run gem install minitest to get MiniTest too but it's not part of the standard library there.
Let's start with a ridiculously simplistic Test::Unit style test:
require 'test/unit'
class TestArray < Test::Unit::TestCase
def test_array_can_be_created_with_no_arguments
assert_instance_of Array, Array.new
end
def test_array_of_specific_length_can_be_created
assert_equal 10, Array.new(10).size
end
end
Nothing too unusual there, I hope. Let's convert it to MiniTest::Spec:
require 'minitest/spec'
require 'minitest/autorun'
describe Array do
it "can be created with no arguments" do
Array.new.must_be_instance_of Array
end
it "can be created with a specific size" do
Array.new(10).size.must_equal 10
end
end
It's a matter of style and opinion, of course, but I prefer the latter version. MiniTest::Spec brings RSpec-esque matchers and contexts right into the Ruby 1.9 standard library and I hope it will start to make significant inroads into the library and developer tool test suites, replacing raw Test::Unit.
(For the purists out there, minitest/spec is implemented in a single ~300 line Ruby file. It's not a framework and it's easy to walk through the code in 10 minutes. Minimal magic!)
MiniTest::Spec's Matchers / ExpectationsPreviously we just did a check for equality and an object's class, but you're going to want to go a bit further. Here are MiniTest::Spec's key expectations:
obj.must_be(operator, expected)- for example,10.must_be :obj.must_be_close_to- the equivalent ofassert_in_deltaobj.must_be_empty- Fails unless obj.empty?obj.must_be_instance_of(klass)- Fails unlessobj.class == klassobj.must_be_kind_of(klass)- Fails unless obj is of class klass or klass is one of its superclasses.obj.must_be_nilobj.must_be_same_as- tests for true object equalitylambda {}.must_be_silentobj.must_be_within_deltaobj.must_be_within_epsilonobj.must_equal(other)- Does a ==/eql? comparison between two objects.obj.must_include(other)obj.must_match(regex)- A regular expression match, e.g."hello".must_match /\w+/lambda {}.must_output(stdout, [stderr..])- The block should have certain output on stdout or stderr. Set stdout to nil just to check stderr.lambda {}.must_raise(exception)obj.must_respond_to(message)obj.must_throw(sym)
The above are all positive expectations but the opposite ones are easy to build as in most cases you can switch must with wont. For example:
wont_bewont_be_emptywont_be_instance_ofwont_be_kind_ofwont_be_nilwont_be_same_aswont_equalwont_includewont_matchwont_respond_to
Note: If you look at the source for minitest/spec.rb you'll see that the expectations map directly to MiniTest::Unit assertions.
Running MiniTest::Spec Specs In Your Ruby Project / LibraryGenerally, running MiniTest::Spec tests can use the same mechanisms as you would for Test::Unit tests, so there's not much to do if you're already up to speed with T::U.
To get things going with rake just bring rake/testtask into your project's Rakefile, if it's not already there, and make some tweaks:
require 'rake/testtask' Rake::TestTask.new do |t| t.libs.push "lib" t.test_files = FileList['test/*_test.rb'] t.verbose = true end
You'll want to tweak the glob in FileList when you follow a different convention for filenames (e.g. test/test_*.rb or specs/*_spec.rb). It's easily updated and gives you rake test for the running.
So next time you're starting on a new library and you're focusing on Ruby 1.9 (it's about time :-)), give MiniTest::Spec a try. You get a neat testing syntax and it's all part of the standard library. (If Ruby 1.8 compatibility is still important, of course, you could even just add 'minitest' to your Gemfile.)
If Ruby 1.9 interests you specifically, check out The Ruby 1.9 Walkthrough, a mega screencast aimed at Ruby 1.8.7 developers who want to learn all about what's new, what's gone, and what's different in Ruby 1.9.2 and 1.9.3.
Further Reading- Using MiniTest::Spec with Rails
- From Test::Unit & Shoulda To MiniTest::Spec & MiniShoulda
- MiniTest: Ruby 1.9's Test Framework
- MiniTest::Mock - the simple mocking system included in MiniTest.
Peter here! :-) I've been running an online course called Ruby Reloaded over the past couple of months for intermediate Ruby developers who want to revise the basics and pick up some new tricks. The next run will probably be in October or November so if you want to be on the waiting list or just learn more about the course, click here.