16.1. Testing with Test::UnitThe "standard" way to do unit testing in Ruby is with Nathaniel Talbott's Test::Unit. This has been distributed with Ruby since 2001. The Test::Unit library uses reflection to analyze your test code. When you subclass the Test::Unit::TestCase class, any methods named starting with test are executed as test code. require 'test/unit' class TC_MyTest < Test::Unit::TestCase def test_001 # ... end def test_002 # ... end # ... end The methods do not have to be numbered as shown. That tends to be my personal convention, but there are certainly others. It is inadvisable, arguably incorrect, for the behavior of the tests to rely on the order in which they are run. However, Test::Unit does in fact run them in alphabetical (or lexicographic) order; I tend to number the methods so that as I watch them being executed I will have some "feel" as to where the test process is in its sequence. Another convention I have used is to put a "title" on the method name (describing the scope or purpose of the test): def test_053_default_to_current_directory # ... end def test_054_use_specified_directory # ... end It's also not a bad idea to put at least a one-line comment describing the purpose and meaning of the test. In general, each test should have one purpose. What if we need to do some kind of setup that takes a long time? It's not practical to do it for every single test, and we can't put it inside a test method (since tests should not be order-dependent). If we need to do special setup for each test, we can create setup and teardown methods for the class. It might seem counterintuitive, but these methods are called for every test. If you want to do some kind of setup only once, before any/all of the tests, you could put that in the body of the class, before the test methods (or even before the class itself). But what if we want to do a corresponding teardown after all the tests? For technical reasons (because of the way Test::Unit works internally), this is difficult. The "best" way is to override the suite's run method (not the class's run method) so as to "wrap" its functionality. Look at the example in Listing 16.1. Listing 16.1. Setup and Teardown
You probably won't find yourself doing this kind of thing often. We'll look at the suite method and its real purpose shortly, but first let's look more at the details of the tests. What goes inside a test? We need to have some way of deciding whether a test passed or failed. We use assertions for that purpose. The simplest assertion is just the assert method. It takes a parameter to be tested and an optional second parameter (which is a message); if the parameter tests true (that is, anything but false or nil), all is well. If it doesn't test true, the test fails and the message (if any) is printed out. Some other assertion methods are as follows (with comments indicating the meaning). Notice how the "expected" value always comes before the "actual" value; this is significant if you use the default error messages and don't want the results to be stated backwards. assert_equal(expected, actual) # assert(expected==actual) assert_not_equal(expected, actual) # assert(expected!=actual) assert_match(regex, string) # assert(regex =~ string) assert_no_match(regex, string) # assert(regex !~ string) assert_nil(object) # assert(object.nil?) assert_not_nil(object) # assert(!object.nil?) Some assertions have a more object-oriented flavor: assert_instance_of(klass, obj) # assert(obj.instance_of? klass) assert_kind_of(klass, obj) # assert(obj.kind_of? klass) assert_respond_to(obj, meth) # assert(obj.respond_to? meth) Some deal specifically with exceptions and thrown symbols. Naturally these will have to take a block: assert_nothing_thrown { ... } # no throws assert_nothing_raised { ... } # no exceptions raised assert_throws(symbol) { ... } # throws symbol assert_raises(exception) { ... } # throws exception There are several others, but these form a basic complement that will cover most of what you will ever need. For others, consult the online documentation at http://ruby-doc.org. There is also a flunk method, which always fails. This is more or less a placeholder. When you run a test file and do nothing special, the console test runner is invoked by default. This gives us feedback using good old-fashioned 1970s technology. There are other test runners also, such as the graphical Test::Unit::UI::GTK::TestRunner. Any test runner may be run by invoking its run method and passing in a special parameter representing the set of tests: class MyTests < Test::Unit::TestCase # ... end # Making it explicit... runner = Test::Unit::UI::Console::TestRunner runner.run(MyTests) The parameter is actually any object that has a suite method that returns an object that is a suite of tests. What does this mean? Let's look more at the concept of a suite of tests. As it happens, a suite of tests can consist of a set of tests or a set of subsuites. Therefore it's possible to group tests together so that only a single set of tests may be run, or all the tests may be run. For example, suppose you have three sets of test cases and you want to run them as a suite. You could do it this way: require 'test/unit/testsuite' require 'tc_set1' require 'tc_set2' require 'ts_set3' class TS_MyTests def self.suite mysuite = Test::Unit::TestSuite.new mysuite << TC_Set1.suite mysuite << TC_Set2.suite mysuite << TS_Set3.suite return mysuite end end Test::Unit::UI::Console::TestRunner.run(TS_MyTests) However, this is unnecessarily difficult. Given the separate test cases, Test::Unit is smart enough to traverse the object space and combine all the test suites it finds into one. So this following code works just as well (even invoking the default test runner as usual): require 'test/unit' require 'tc_set1' require 'tc_set2' require 'ts_set3' There is more to Test::Unit than we've seen here; it's also likely to have some improvements made in the future. Always do an online search for the latest information. |