Movatterモバイル変換


[0]ホーム

URL:


Search |Blog |Contact

Unit Testing C and C++ ... with Ruby and RSpec!110

Posted byDean WamplerTue, 05 Feb 2008 04:08:00 GMT

If you’re writing C/C++ code, it’s natural to write your unit tests in the same language (or use C++ for your C test code). All the well-known unit testing tools take this approach.

I think we can agree that neither language offers the best developer productivity among all the language choices out there. Most of us use either language because of perceived performance requirements, institutional and industry tradition,etc.

There’s growing interest, however, in mixing languages, tools, andparadigms to get the best tool for a particular job. <shameless-plug>I’m giving a talk March 7th at SD West on this very topic, called Polyglot and Poly-Paradigm Programming </shameless-plug>.

So, why not use a more productive language for your C or C++ unit tests? You have more freedom in your development chores than what’s required for production. Why not use Ruby’s RSpec, a Behavior-Driven Development tool for acceptance and unit testing? Or, you could use Ruby’s version of JUnit, called Test::Unit. The hard part is integrating Ruby and C/C++. If you’ve been looking for an excuse to bring Ruby (or Tcl or Python or Java or…) into your C/C++ environment, starting with development tasks is usually the path of least resistance.

I did some experimenting over the last few days to integrate RSpec usingSWIG (Simplified Wrapper and Interface Generator), a tool for bridging libraries written in C and C++ to other languages, like Ruby. The Ruby section of theSWIG manual was very helpful.

My Proof-of-Concept Code

Here is a zip file of my experiment:rspec_for_cpp.zip

This is far from a complete and working solution, but I think it shows promise. See theCurrent Limitations section below for details.

Unzip the file into a directory. I’ll assume you named itrspec_for_cpp. You need to havegmake,gcc,SWIG and Ruby installed, along with the RSpec “gem”. Right now, it only builds onOS X and Linux (at least the configurations on my machines running those OS’s – see the discussion below). To run the build, use the following commands:

        $ cd rspec_for_cpp/cpp        $ make

You should see it finish with the lines

        ( cd ../spec; spec *_spec.rb )        .........        Finished in 0.0***** seconds        9 examples, 0 failures

Congratulations, you’ve just tested some C and C++ code with RSpec! (Or, if you didn’t succeed, see the notes in theMakefile and the discussion below.)

The Details

I’ll briefly walk you through the files in the zip and the key steps required to make it all work.

cexample.h

Here is a simple C header file.

        /* cexample.h */        #ifndef CEXAMPLE_H        #define CEXAMPLE_H        #ifdef __cplusplus         extern "C" {        #endif        char* returnString(char* input);        double returnDouble(int input);        void  doNothing();        #ifdef __cplusplus         }        #endif        #endif

Of course, in a pure C shop, you won’t need the#ifdef __cplusplus stuff. I found this was essential in my experiment when I mixed C and C++, as you might expect.

cpp/cexample.c

Here is the corresponding C source file.

        /* cexample.h */        char* returnString(char* input) {            return input;        }        double returnDouble(int input) {            return (double) input;        }        void  doNothing() {}

cpp/CppExample.h

Here is a C++ header file.

        #ifndef CPPEXAMPLE_H        #define CPPEXAMPLE_H        #include <string>        class CppExample         {        public:            CppExample();            CppExample(const CppExample& foo);            CppExample(const char* title, int flag);            virtual ~CppExample();            const char* title() const;            void        title(const char* title);            int         flag() const;            void        flag(int value);            static int countOfCppExamples();        private:            std::string _title;            int         _flag;        };        #endif

cpp/CppExample.cpp

Here is the corresponding C++ source file.

        #include "CppExample.h"         CppExample::CppExample() : _title("") {}        CppExample::CppExample(const CppExample& foo): _title(foo._title) {}        CppExample::CppExample(const char* title, int flag) : _title(title), _flag(flag) {}        CppExample::~CppExample() {}        const char* CppExample::title() const { return _title.c_str(); }        void        CppExample::title(const char* name) { _title = name; }        int  CppExample::flag() const { return _flag; }        void CppExample::flag(int value) { _flag = value; }        int CppExample::countOfCppExamples() { return 1; }

cpp/example.i

Typically inSWIG, you specify a.i file to theswig command to define themodule that wraps the classes and global functions, which classes and functions to expose to the target language (usually all in our case), and other assorted customization options, which are discussed in theSWIG manual. I’ll show theswig command in a minute. For now, note that I’m going to generate anexample_wrap.cpp file that will function as the bridge between the languages.

Here’s myexample.i, where I named the moduleexample.

        %module example        %{            #include "cexample.h"             #include "CppExample.h"            %}        %include "cexample.h"         %include "CppExample.h"

It looks odd to have header files appear twice. The code inside the%{...%} (with a ’#’ before eachinclude) are standard C and C++ statements,etc. that will be inserted verbatim into the generated “wrapper” file,example_wrap.cpp, so that file will compile when it references anything declared in the header files. The second case, with a ’%’ before theinclude statements1, tellsSWIG to make all the declarations in those header files available to the target language. (You can be more selective, if you prefer…)

Following Ruby conventions, the Ruby plugin forSWIG automatically names the module with an upper case first letter (Example), but you userequire 'example' to include it, as we’ll see shortly.

Building

See thecpp/Makefile for the gory details. In a nutshell, you run theswig command like this.

        swig -c++ -ruby -Wall -o example_wrap.cpp example.i

Next, you create a dynamically-linked library, as appropriate for your platform, so the Ruby interpreter can load the module dynamically when required. TheMakefile can do this for Linux andOS X platforms. See the Ruby section of theSWIG manual for Windows specifics.

If you test-drive your code, which tends to drive you towards minimally-coupled “modules”, then you can keep your libraries and build times small, which will make the build and test cycle very fast!

spec/cexample_spec.rb andspec/cppexample_spec.rb

Finally, here are the RSpec files that exercise the C and C++ code. (Disclaimer: these aren’t the best spec files I’ve ever written. For one thing, they don’t exercise all theCppExample methods! So sue me… :)

        require File.dirname(__FILE__) + '/spec_helper'        require 'example'        describe "Example (C functions)" do          it "should be a constant on Module" do            Module.constants.should include('Example')          end          it "should have the methods defined in the C header file" do            Example.methods.should include('returnString')            Example.methods.should include('returnDouble')            Example.methods.should include('doNothing')          end        end        describe Example, ".returnString" do          it "should return the input char * string as a Ruby string unchanged" do            Example.returnString("bar!").should == "bar!"           end          end        describe Example, ".returnDouble" do          it "should return the input integer as a double" do            Example.returnDouble(10).should == 10.0          end        end        describe Example, ".doNothing" do          it "should exist, but do nothing" do            lambda { Example.doNothing }.should_not raise_error          end        end

and

    require File.dirname(__FILE__) + '/spec_helper'    require 'example'    describe Example::CppExample do      it "should be a constant on module Example" do        Example.constants.should include('CppExample')      end    end    describe Example::CppExample, ".new" do      it "should create a new object of type CppExample" do        example = Example::CppExample.new("example1", 1)        example.title.should == "example1"         example.flag.should  == 1      end    end    describe Example::CppExample, "#title(value)" do      it "should set the title" do        example = Example::CppExample.new("example1", 1)        example.title("title2")        example.title.should == "title2"       end    end    describe Example::CppExample, "#flag(value)" do      it "should set the flag" do        example = Example::CppExample.new("example1", 1)        example.flag(2)        example.flag.should == 2      end    end

If you love RSpec like I do, this is a very compelling thing to see!

And now for the small print:

Current Limitations

As I said, this is just an experiment at this point. Volunteers to make thisbattle-ready would be most welcome!

General

The ExampleMakefile File

It Must Be Hand Edited for Each New or Renamed Source File

You’ve probably already solved this problem for your own make files. Just merge in the exampleMakefile to pick up theSWIG- and RSpec-related targets and rules.

It Only Knows How to Build Shared Libraries for MacOS X and Linux (and Not Very Well)

Some definitions are probably unique to myOS X and Linux machines. Windows is not supported at all. However, this is also easy rectify. Start with the notes in theMakefile itself.

Themodule.i File Must Be Hand Edited for Each File Change

Since the format is simple, a make task could fill a template file with the changed list of source files during the build.

Better Automation

It should be straightforward to provide scripts,IDE/Editor shortcuts,etc. that automate some of the tasks of adding new methods and classes to your C and C++ code when you introduce themfirst in your “spec” files. (The trueTDD way, of course.)

Specific Issues for C Code Testing

I don’t know of any other C-specific issues, so unit testing with Ruby is most viable today for C code. Although I haven’t experimented extensively, C functions and variables are easily mapped bySWIG to a Ruby module. The Ruby section of theSWIG manual discusses this mapping in some detail.

Specific Issues for C++ Code Testing

More work will be required to make this viable. It’s important to note thatSWIG cannot handle all C++ constructs (although there are workarounds for most issues, if you’re committed to this approach…). For example, namespaces, nested classes, some template and some method overloading scenarios are not supported. TheSWIG manual has details.

Also, during my experiment,SWIG didn’t seem to mapconst std::string& objects in C++ method signatures to Ruby strings, as I would have expected (char* worked fine).

Is It a Viable Approach?

Once theGeneral issues listed above are handled, I think this approach would work very well for C code. For C++ code, there are more issues that need to be addressed, and programmers who are committed to this strategy will need to tolerate some issues (or just use C++-language tools for some scenarios).

Conclusions: Making It Development-Team Ready

I’d like to see this approach pushed to its logical limit. I think it has the potential to really improve the productivity of C and C++ developers and the quality of their test coverage, by leveraging the productivity and power of dynamically-typed languages like Ruby. If you prefer, you could use Tcl, Python, even Java instead.

License

This code is complete open and free to use. Of course, use it at your own risk; I offer it without warranty,etc.,etc. When I polish it to the point of making it an “official” project, I will probably release under the Apache license.

1 I spent a lot of time debugging problems because I had a ’#’ where I should have had a ’%’!Caveat emptor!

Comments

Leave a response

  1. Avatar
    masters in criminal justiceover 3 years later:
Comments

leave url/email »)

Spinner

Blog Search

Searching...

Follow us on twitter

Mentortwitter id
Uncle Bobunclebobmartin
Brett Schuchertschuchert
Michael Feathersmfeathers
Bob Kossbob_koss

Categories

Blogroll

  • "But Uncle Bob" Archive
  • David Chelimsky's other blog
  • Michael Feathers' other blog
  • Dean Wampler's other blog
  • James Grenning's blog

Syndicate



Object Mentor Blog

  • powered by typo / styled with OM-scribbish

New York

Columbia

Newark

San Diego

Knoxville

Clearfield

Quakertown

Dublin

Lake Buena Vista

San Jose

Smyrna

Irving

Indianapolis

Doral

Young Harris

Newport Beach

London

Wesley Chapel

Cancun

Amsterdam

Naples Park

Kansas City

Socastee

Charleston

Redondo Beach

Steamboat Springs

Fort Lauderdale

Berkeley

Mexico City

Zanzibar

Four Corners

Cocoa Beach

Denver

Praiano

Seattle

Atlanta

Arlington

Arlington

Chicago

Los Angeles

Pensacola Beach

Columbus

Gleneden Beach

Alexandria

Nusa Dua (Bali)

Honolulu

Colorado Springs

Salem

Dimondale

Marina di Grosseto

S'Agaro

Sozopol


[8]ページ先頭

©2009-2025 Movatter.jp