Section 16.2. The ZenTest Tools


16.1. Testing with Test::Unit

The "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

require 'test/unit' class MyTest < Test::Unit::TestCase   def self.major_setup     # ...   end   def self.major_teardown     # ...   end   def self.suite     mysuite = super          # call the higher-level suite     def mysuite.run(*args)   # Now add a singleton method       MyTest.major_setup       super       MyTest.major_teardown     end     mysuite                  # and return the new value.   end   def setup     # ...   end   def teardown     # ...   end   def test_001     # ...   end   def test_002     # ...   end   # ... end

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.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net