Motivations

Ruby is a powerful yet easy to use scripting language, which is ideal for rapid prototyping and testing as it trades runtime performance for "humanity". In other words, Ruby is designed to optimize the developer not the machine.

Ruby test code can be written in a much concise way compared to other programming language of high run-time performance such as C++. For example, the task "Reverse each word of a sentence" usually requires about dozens of lines of C++ code while in Ruby, we can achieve this by one line:

result = testSentence.split(' ').map {|w| w.reverse}.join(' ')

Fortunately, some great APIs such as SWIG exists for wrapping C/C++ code blocks into other languages. The flexibility of scripting language can be added to yield a more efficient development cycle.

As this tutorial is only a lead-in, I will only talk about my hands-on experience on how to use Ruby::Test::Uint module and SWIG to do unit testing for C++ program under MAC OSX. (Routines may change slightly due to platform difference.) It is designed for developers who prefer drinking a cup of coffee while waiting for the test to finish in hours to writing test code for hours.

Preliminary

Build and install the following tools were they necessary
C++: http://en.wikipedia.org/wiki/C%2B%2B
Ruby: http://www.ruby-lang.org
SWIG: http://www.swig.org
(Optional) CMake: http://www.cmake.org

For MAC OS 10.6, these development packages have already been pre-installed as bundles.

Learn by Example


First take a look at our target C++ source file,

// File: example.cxx

const double My_variable = 3.0;

/* Compute factorial of n */
int  fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

/* Compute n mod m */
int my_mod(int n, int m) {
    return(n % m);
}

Presume our goal is to unit-test the "My_variable" const and the "fact" function in file "example.cxx", so first we need to write a SWIG interface file to tell which part of C++ code needs to be wrapped. The file is shown as follows,

// File: example.i
%module example

%{
// Put headers and other declarations here

const double My_variable = 3.0;
int fact( int );

%}

const double My_variable = 3.0;
int fact( int );

It looks a little strange that the function "fact" appears twice in the file, but for now we just need to know that it is the correct way...

Next, open up a terminal, change directory to a folder containing the two source files we mentioned above (example.cxx and example.i)
Build the project and produce a shared lib for Ruby to load as follows,

$ swig -c++ -ruby example.i
$ g++ -c example.cxx
$ g++ -c example_wrap.cxx -I/usr/lib/ruby/1.8/universal-darwin10.0
$ g++ -bundle -flat_namespace -undefined suppress example.o example_wrap.o -o example.bundle

Note if you are not sure where is the Ruby header files, take the advantage of Ruby global variable "$:" as follows,

$ ruby -e 'puts $:.join("\n")'

At this phase, we have successfully generated a shared library "example.bundle" which contains the information of the "fact" function we want to test and the shared library file that is also recognized by Ruby. Next, we can either enter into the interactive Ruby environment to perform some intuitive tests or write a ruby test with Test::Unit or rspec modules.

For example, if we want to use the interactive Ruby environment, try as follows in terminal,

$ irb

This should start the interactive Ruby environment, then type the following command in IRB

>> require 'example'

If everything goes well, the "expression" should return value "true". A test can be performed as follows. (Remember the naming convention of module in Ruby: module name should be capitalized)

>> Example.fact(-1)
=> 1
>> Example.fact(4)
=> 24

More formal test code with Ruby Test::Unit module is also very straightforward, one can check their Wiki pages for more details at http://en.wikibooks.org/wiki/Ruby_Programming/Unit_testing

For example, we can write our own test case as follows,

# File: ts_example.rb

require 'example'
require 'test/unit'

# Define test suite TestExample
class TestExample < Test::Unit::TestCase
 
  # test case test_simple
  def test_simple
    # for func fact
    assert_equal( 1, Example.fact( -1 ) )
    assert_equal( 24, Example.fact( 4 ) )
   
    # for const var My_variable = 3.0
    assert_in_delta( 3.0, Example.My_variable, 1e-20 )
    assert_not_equal( 3.0000001, Example.My_variable )
    # const can not be reassigned in C++
    assert_raise( NoMethodError ) { Example.My_variable = 1.0 }
   
  end
 
  # other test cases go here
 
end

Then we can run a specified test named "test_simple" in terminal like this,

$ ruby -w tc_example.rb --name test_simple

A similar result will show as below,

Loaded suite ts_example
Started
.
Finished in 0.067469 seconds.

1 tests, 5 assertions, 0 failures, 0 errors

My working tutorial code along with a bash script file and a CMakeLists file are attached here. Anyone is welcomed to have a try.

Note: If you use the CMakeLists file do remember to rename the shared library output from "libexample.so" to "example.bundle".

Summary

The about example shown in this post is only a Minimal Working Example (MWE) on how to use SWIG and Ruby to unit-test simple C++ code under MAC OSX. More comprehensive usage can be found in SWIG::Ruby module documentation at
http://www.swig.org/Doc1.3/Ruby.html

Enjoy!

EndNotes


The only major C++ feature not currently supported by SWIG as of version 2.0 is the wrapping of nested classes. So if the testing unit links to some highly-templated C++ libraries such as ITK, additional effort is needed. For the user of ITK, wrapITK as of version 0.3 fully supports wrapping ITK to Java, Python, and Tcl (although no Ruby support at the moment).