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.
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.
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/
.
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):
raise "This should never happen!"
,
then exception is RuntimeError
.
obj1.send(op, obj2)
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.
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>