A Tutorial: C++ Unit testing with Ruby
January 20, 2011Ruby 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(' ')
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);
}
// 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 );
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
$ ruby -e 'puts $:.join("\n")'
For example, if we want to use the interactive Ruby environment, try as follows in terminal,
$ irb
>> require 'example'
>> Example.fact(-1)
=> 1
>> Example.fact(4)
=> 24
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
$ ruby -w tc_example.rb --name test_simple
Loaded suite ts_example
Started
.
Finished in 0.067469 seconds.
1 tests, 5 assertions, 0 failures, 0 errors
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).
Posted by Wei Lu. Posted In : Testing