Sunday, 2 May 2010

Ruby Unit Testing

Ruby has a built-in unit testing facility, Test::Unit. To use it, create a new file, and define a class that inherits from Test::Unit::TestCase. This is best done in a separate folder, by the way, so you can easily release your project without the unit tests (Rails sets this all up for yu by the way).
# Load in the unit test classes
require 'test/unit'
# Load in your class to be tested
require 'things'

class ThingsTest < Test::Unit::TestCase

def setup
# Set up some test conditions here
# This will be invoked before each test

def teardown
# This will be invoked after each test
# Use to close connections, etc.

You will also need to define your tests. Each test goes in its own method, the name of which must begin "test" (as Ruby will search your class for such methods for testing).
def test_thing_creates_okay
# assertions

There are several assertions you can use to test the results. Here are some of the more common ones:
assert_equal      # Compares two objects
assert # Tests for true
assert_nil # Tests for nil
assert_raise # Tests an exception is thrown
assert_in_delta # Test two floats are within a given amount

Rails (since about 2.2) uses a different format for tests:
test "Thing creates okay" do
# assertions

The end result is the same. In Rails, test is a method that dynaally defines a method (in this case called "test_thing_creates_okay") with the given block.

Running Tests

If you are using NetBeans you can run a test simply by right clicking on the file being tested, and selecting test. NetBeans can work out which the relevant test is as long as it is named conventionally (eg the test for things.rb would be things_test.rb). Alternatively, you can right click on the test file, and selecting test or run.

To test from the command prompt, simply run the test file through ruby (eg ruby things_test.rb; if you are using JRuby, do jruby things_test.rb).

For a big project, you can collect all your tests together to form a test suite. Just create a Ruby file that requires each of the tests in turn, and run that through Ruby. Alternativerly, put this in your test suite, and it will automatically go though every file in that folder that has a name ending "_test.rb".
files = Dir.entries(File.dirname(__FILE__)).select do |file|
file =~ /_test.rb$/
files.each { |file| require file }

Testing private methods and variables

Here is a trivial class with an instance variable and a private method.
class UnitTestee
def initialize x
@val = x


def do_stuff y
@val * @val * y

I can test the private method using send, like this:
def test_do_stuff
@ut = 12
assert_equal 12 * 12 * 5, @ut.send(:do_stuff, 5)

Note that I have left Ruby to calculate 12 * 12 * 5; not only does this save me doing it, it makes it clearer where the number comes from (even better to use named constants, of course).

To access the instance variable, I can define a new method. Doing that in my test file means that the new method is not part of the API. Here is one way to do it (there are others):
def test_variable
@ut = 12
eval "def @ut.get_val; @val; end"
assert_equal 12, @ut.get_val


Test files often include something like this at the start:
$:.unshift File.join(File.dirname(__FILE__),'..','lib')

This simply adds the code folder (called "lib" here) to the load path, so Ruby can find the file to be tested.

Struggling with Ruby: Contents Page

No comments: