Contents
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
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:
#include "unit--.h"
testSuite(MySuite);
testCase(CompareCase, MySuite)
{
int x = 1;
int y = x + 2;
assertTrue(x < y);
}
The closeValue()
function could be used to test the closeness between floating point values. It resides in the namespace unit_minus
.
testCase(Closeness, MySuite)
{
using namespace unit_minus;
double a = 0.73;
double b = 2.19;
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
.
Unit-- provides an assertNoArrive()
macro for us to deal with exception related tests.
testCase(ExceptionCase, MySuite)
{
try {
throw "going to catch clause";
assertNoArrive("exception not thrown");
}
catch (const char*) {
}
}
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:
- Build an executable for the "unit test program", which contains all the test cases
- 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.
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:
#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:
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.
# 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.
# 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:
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:
- Choose Project --> Settings... --> Settings for Win32 Debug.
- Find the "Executable for debug session" in the "Debug" tab, and copy the full path of the executable file.
- 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".
- Do step 2 and 3 for Win32 Release.
- 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".
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.
- 2006-06-13 - initial version.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.