Click here to Skip to main content
15,887,135 members
Articles / Desktop Programming / MFC
Article

Unit--, a simple and easy-to-use unit test aid for C++

Rate me:
Please Sign up or sign in to vote.
4.75/5 (12 votes)
14 Jun 20065 min read 32K   909   26  
An article describing a portable unit test framework for C++.

unit-- logo, thank bob for designing

Contents

Introduction

Unit-- is a light-weight unit test framework for C++. It provides the necessary features to help us build portable unit tests easily:

  • declare and register test case at once
  • test suite support
  • simple (only one header and one source file)
  • assertion for bool values, equality, and closeness
  • exception handling
  • need no RTTI
  • integration with IDEs to support TDD

Using the code

Creating a test case

There are three basic macros, namely testSuite, testCase, and assertTrue. They are used to build test cases, and the cases will register themselves to the unit-- framework. Here are the basic steps:

C++
// 1. include the header file
#include "unit--.h"

// 2. define your own test suites to organize test cases
testSuite(MySuite);

// 3. define test cases, and attach them to some suite
testCase(CompareCase, MySuite)
// 4. write your test code
{
    int x = 1;
    int y = x + 2;
    assertTrue(x < y);
}

More utilities

The closeValue() function could be used to test the closeness between floating point values. It resides in the namespace unit_minus.

C++
testCase(Closeness, MySuite)
{
    // expose the namespace
    using namespace unit_minus;

    double a = 0.73;
    double b = 2.19;

    // test if the 2nd parameter is close enough to the 1st
    // with precision specified by the 3rd parameter
    assertTrue(closeValue(b, a * 4, 0.001));
}

If the values are not close enough, we get a simple message reading:

C:\unitmm\haha.cpp(24) : error: 
      Assertion failed: <closeValue(b, a * 4, 0.001)>

It would be better if the exact values could be printed with the message. Simply add an "Info" suffix to achieve it:

assertTrue(closeValueInfo(b, a * 4, 0.001));
C:\unitmm\haha.cpp(24) : error: expected <2.19>, 
        but was <2.92>, not close enough with precision <0.001>

To test equality, there are similar equalValue() and equalValueInfo() functions, also residing in the namespace unit_minus.

Exception handling

Unit-- provides an assertNoArrive() macro for us to deal with exception related tests.

C++
testCase(ExceptionCase, MySuite)
{
    // check if an exception is thrown as expected
    try {
        throw "going to catch clause";

        // if an exception is thrown, the code
        // following it should not be executed
        assertNoArrive("exception not thrown");
    }
    // try to catch the exact type.
    // if the exception is not of this type, unit-- would catch it
    // do NOT use catch(...), or unit-- will miss the failure
    catch (const char*) {
    }
}

Integration with IDEs

Integrating with IDEs could be helpful for Test Driven Development (TDD). Imagine you are coding with some IDE. The "Compile" button would probably be pressed from time to time. If it announces an error (even a warning for some seasoned developers), you just click the reporting line, and the IDE will lead you to the corresponding source line.

That's what we want in TDD. You write code, and press the Compile button from time to time. Because you write the test before the code, there should be some assertion failure report before you finish. And you just click the reporting line to check what happened, and the IDE leads you to the corresponding assertion in some test case immediately.

To achieve that, the IDE should do the following, every time the project is being built:

  1. Build an executable for the "unit test program", which contains all the test cases
  2. Run the executable before the project itself is built

Let's check an example:

Suppose there's a simple project which has only one function, which should return x powered by 3.

C++
int cubic(int x);

The project's directory structure looks like this:

+- unitmm_demo
   +- src
        +- cubic.h
        +- cubic.cpp
   +- test
        +- test_cubic.cpp
        +- unit--.h
        +- unit--.cpp

Among all the files, cubic.h and cubic.cpp contains the definition of the function cubic(), test_cubic.cpp contains the unit test case, and unit--.h and unit--.cpp contain the unit-- framework.

For illustration purposes, cubic.cpp contains an apparently incorrect implementation:

C++
#include "cubic.h"

int cubic(int x)
{
    return x;
}

The unit test program should run as a step of the building process. The configuration depends on what platform and IDE you are working with. The author chose two of the main-stream platform/IDEs to make a demonstration:

Unix/Linux with g++ and Emacs

The example uses g++ (3.0 or above), GNU make, and GNU Emacs as the IDE.

First, write unitmm_demo/test/makefile for the unit test. Please pay attention to "all", the unit test program will be launched automatically by make.

C++
# makefile for unit test

CXX=g++
CXXFLAGS=-Wall -O2                             # compile flags
OBJS= unit--.o  ../src/cubic.o  test_cubic.o   # link with unit--.cpp
RUNNER=a.exe                                   # executable file for unit test

# compile command line
.cpp.o:
    ${CXX} -c -o $@ ${CXXFLAGS} $<

.PHONY: all clean

all: ${RUNNER}
    ./${RUNNER} format"../test/_f:_l: _m"

${RUNNER}: ${OBJS}
    ${CXX} ${CXXFLAGS} -o$@ ${OBJS}

clean:
    rm -f ${RUNNER} ${OBJS}

The unit test program has a single command line argument beginning with "format", which specifies the format of the assertion failure report. "_f" will be replaced by the corresponding source file name, "_l" (lower case L) by the line number, and "_m" by the reporting message.

The unitmm_demo/src/makefile is for the project itself. Once again, please look at "all". The unit test program will be launched every time you build the project.

C++
# makefile for the project

CXX=g++
CXXFLAGS=-Wall -O2              # compile flags
OBJS=cubic.o

.cpp.o:
    ${CXX} -c -o $@ ${CXXFLAGS} $<

.PHONY: all clean

all: ${OBJS}
    cd ../test && make all      # invoke unit test

clean:
    rm -f ${OBJS}
    cd ../test && make clean

Finally, open GNU Emacs, find cubic.cpp, and type <M-x> compile <Ret> to launch make. Hurray, the assertion failure is reported in the *compilation* buffer! Press <Ret> on the reporting line, Emacs will lead you to the failed assertion. Here is a screenshot:

screen shot of unit-- with emacs

Windows with VC++

We will use Microsoft Visual C++ 6 as an example here.

First, there need to be a Win32 Console Application project containing all the five source code files.

Then, do the following steps:

  1. Choose Project --> Settings... --> Settings for Win32 Debug.
  2. Find the "Executable for debug session" in the "Debug" tab, and copy the full path of the executable file.
  3. Go to "Post-build step" tab, create a new command, and paste the full path with lower case "vc" as the command line parameter. E.g.: ".\Debug\my_test_prj.exe vc".
  4. Do step 2 and 3 for Win32 Release.
  5. Click the OK button.

Select Build --> Rebuild All from the main menu, and you will get an error. It's not a compile time error. Actually, it is an assertion failure, but double clicking on the reporting line will lead you to the corresponding failed assertion, just like a compile time error.

You can "File --> New" another project in the same workspace, for the real task you are working on, and set the new project to depend on the test project with "Project --> Dependencies". Thus, the unit test program will run whenever you build the "real project".

Conclusion

Unit-- is a simple and easy-to-use unit test framework for C++. Integrating it with the IDE could help you apply Test Driven Development to your projects.

History

  • 2006-06-13 - initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --