RubyUnit -- Hugh Sasse's Docs

Contents

Purpose of this document
Acknowledgements
Introduction
Ruby Specific Techniques for Testing
which includes a list of assertions available.
Template code for testing a "library" file

Purpose of the this document

RubyUnit is the Unit Testing framework for the Ruby programming language. On first encountering RubyUnit I could not see how to use it. This was because I could not see the structure in the examples. I am therefore creating this to assist myself and others who want to use RubyUnit to improve their software.

Acknowledgements

Thanks to Dave Thomas and Aleksi Niemelä providing and pointing me to much of the information in here. Thanks are also due to Don Wells, Ron Jeffries and Brian Marick. And thank you to Masaki Suketa for creating RubyUnit.

Introduction

RubyUnit is based on the idea of "unit testing". central to the discipline of Extreme Programming. For any object you wish to create, you need to have an understanding of how it will behave in response to messages or methods. Clearly this understanding is essential to be able to write the code for the object, but this same understanding is used in the testing of the object. Therefore it is useful to create a test or number of tests for correct behaviour of the object before the object is written. When people refer to unit testing, the "unit" they are testing is usually a class.

This is one of the fundamentals of Extreme Programming, but it is not the whole thing. Other aspects of extreme programming include getting definitions of what the user wants a particular thing to do (User Stories), using these to write the Unit Tests, only implementing and testing a number of user stories at once, not writing more than you need to write at once, and continual re-evaluation of the project plan.

It has been suggested that unit tests are written before the code. This means that the tests act as a form of specification for the code, and assist the programmer in defining what the code should do in given circumstances. This gives similar benefits to those gained from using Design by Contract; it avoids programming by coincidence. (In Design by Contract a method or object only agrees to do certain things and guarantee certain post-conditions, provided that stated preconditions are met. If these preconditions are not met then nothing is guaranteed.) Another (psychological) benefit of this technique is that the code is developed until it works correctly, rather than being developed, and then found not to be working correctly. This results in a more positive attitude to testing.

Other things to consider are how one does unit tests on code that must interact with other software. One way is to use Mock Objects. There is a PDF document about this, summarised here. The document mentions decoupling the interface using another object. Kelby Zorgdrager's article "Developing Java solutions using Design Patterns" suggest that the command patterm might be the way to do this.

Other information about Extreme Programming can be found on Martin Fowler's Extreme Programming page. There are also significant resources available at http://www.xprogramming.com/.

Ruby Specific Techniques for Testing

As well as RubyUnit there is also Test::Unit, which is described on the UsingTestUnit page on the Ruby Garden Wiki. The Test::Unit home page is at http://testunit.talbott.ws/. The ZenTest software for creating the skeleton test suite is available from http://www.zenspider.com/ZSS/Products/ZenTest. There is an article about these at http://www.linuxjournal.com/article.php?sid=7776. There is also further documentation in RDoc form on the Test::Unit site.

RubyUnit has the facilities needed to perform unit testing in Ruby. To use RubyUnit, in the testing program you must:

require "runit/testcase"
require 'runit/cui/testrunner'
require 'runit/testsuite'
require "myclass"

This assumes that you have downloaded and installed RubyUnit so that Ruby can find the libraries. Instructions come with Rubyunit which tell you how to do that. It also assumes that the class you want to test is obtained using require "myclass".

Then you must write your test class. We will call the example test class Testing_class for now. This needs to be a subclass of RUNIT::TestCase so that it can inherit all the assertion methods etc.

class Testing_class < RUNIT::TestCase
    ...
end

The methods of this class that do the testing must have names beginning with 'test_'. Each method may do one or more tests. Within each such test the "assert" methods of RUNIT::TestCase are used to perform the tests. These methods take an optional message parameter, defaulting to "" (the empty string):

assert_fail(message)
assert(boolean, message="")
assert_equal(expected, actual, message="")
can also be invoked as assert_equals(...)
assert_equal_float(expected, actual, e, message="")
assert_same(expected, actual, message="") # uses equal?
assert_nil(obj, message="")
assert_not_nil(obj, message="")
assert_respond_to(method, obj, message="")
assert_kind_of(c, obj, message="")
assert_instance_of(c, obj, message="")
assert_match(str, re, message="")
can also be invoked as assert_matches(...)
assert_not_match(str, re, message="")
assert_exception(exception, message="") {block}
exception must be some kind of error type. If you cause the exception through raising a String literal, such as raise "This should never happen!", then exception is RuntimeError.
assert_no_exception(*arg) {block}
assert_operator(obj1, op, obj2, message="")
asserts obj1.send(op, obj2)
assert_send(obj1, op, *args)

Each test may require some setup to be done before the test, and if many tests are to be done then initial conditions must be restored after each test is completed. The methods setup and teardown are methods (of the Testing_class class) that provide these two features if necessary. They work by overriding the methods of RUNIT::TestCase of the same name. (Actually, RUNIT::TestCase gets these methods by Mix-in from the RUNIT::Test module, just as it gets the assert* methods by Mix-in from the module RUNIT::Assert.)

Once the tests have been written, they can be run individually with command like:

RUNIT::CUI::TestRunner.run(Testing_class.new("test_this_thing"))
or they can all be run in one go with:
RUNIT::CUI::TestRunner.run(Testing_class.suite)
The suite method of RUNIT::TestCase constructs the list of methods beginning 'test_' (mentioned earlier). (This is done through the constructor of RUNIT::TestSuite, but most of the time this need not concern the user.) suite also calls new() for you.

Template code for testing a "library" file

Where I have a file that is only used through require "my_class", I add something of this sort to the end, so that the tests on My_class are done when the my_class is invoked directly as a program:-

if __FILE__ == $0
    require "runit/testcase"
    require 'runit/cui/testrunner'
    require 'runit/testsuite'

    class  Testing_class < RUNIT::TestCase
        def test_feature1
            mine = My_class.new
            # ... etc
        end
        #...
    end

    RUNIT::CUI::TestRunner.run(Testing_class.suite)
end

It should be noted that there is a script which comes with RubyUnit called c2t.rb. This script takes a class name and a filename and will create on the standard output code to test the class using RubyUnit. Clearly this cannot know all the postconditions for each test, so it can only produce a skeleton, but it produces code similar to the above template, and more.

For example, for my revision class, without any test code (revision0.rb) it produces this.


Created 14-AUG-2000

$Id: ruby-unit.html,v 1.17 2004-09-15 11:55:36+01 hgs Exp hgs $

Last Modified 15-SEP-2004 by Hugh Sasse <hgs@dmu.ac.uk>