I have worked with C++ in my early days of programming (between 1997 and 2000), but I used to work with Borland C++ Builder and Microsoft Visual C++, in that time I have not heard about unit tests yet, after that I worked with Delphi, PHP, ASP, ColdFusion, …
Since 2002 I worked most of the time with Java, and learned a lot since then, lots of good practices, a lot more about object orientation and I learned to love unit tests.
Little time ago I starter working with C++ again, but I’m already in love with unit tests, and want to do them for my C++ code too, and this post is a very short example of how a Java programmer can work with C++ and do unit tests.
A C++ project starts with a Makefile, I’m used with ANT and do not like the idea of enumerating all my source files in a build configuration file like most examples of Makefiles do, so I created a simple but very flexible Makefile for my project.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | TESTDIRECTORIES := test DIRECTORIES := src SOURCES := $(foreach dir,$(DIRECTORIES),$(wildcard $(dir)/*.cpp)) TESTSOURCES := $(foreach dir,$(TESTDIRECTORIES),$(wildcard $(dir)/*.cpp)) OBJECTS := $(patsubst %.cpp,%.obj,$(SOURCES)) TESTOBJECTS := $(patsubst %.cpp,%.obj,$(TESTSOURCES)) TESTOBJECTS += $(filter-out src/main.obj,$(OBJECTS)) TARGET := example LINK := g++ CC := g++ CFLAGS := -c LFLAGS := all: $(OBJECTS) $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) test: $(TESTOBJECTS) $(LINK) $(LFLAGS) -lcppunit -o $(TARGET)_unit $(TESTOBJECTS) ./$(TARGET)_unit %.obj:%.cpp $(CC) $(CFLAGS) -o $*.obj $*.cpp |
With this make file, all cpp files in the src directory will be part of the target executable, more directories can be added just enumerating the source directories in the “DIRECTORIES” variable, the same for the test code, under the test directory and the “TESTDIRECTORIES” variable.
The trick here is the combination of the functions foreach and wildcard, the function pathsubst is used to change the extensions from .cpp to .obj and the filter-out function is used to remove the main.cpp (the entry point for the main application) from the test objects.
This Makefile is the nearest I could get from ANT functionality for C++ programming, but of course it can be improved a lot.
But the Makefile is not the target of this post, I’m writing this post to tell you about CppUnit, a great unit test framework for C++.
I started writing this unit test runner using CppUnit:
testRunner.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <cppunit/CompilerOutputter.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <cppunit/TestResult.h> #include <cppunit/TestResultCollector.h> #include <cppunit/TestRunner.h> #include <cppunit/BriefTestProgressListener.h> int main (int argc, char* argv[]) { // informs test-listener about testresults CPPUNIT_NS :: TestResult testresult; // register listener for collecting the test-results CPPUNIT_NS :: TestResultCollector collectedresults; testresult.addListener (&collectedresults); // register listener for per-test progress output CPPUNIT_NS :: BriefTestProgressListener progress; testresult.addListener (&progress); // insert test-suite at test-runner by registry CPPUNIT_NS :: TestRunner testrunner; testrunner.addTest (CPPUNIT_NS :: TestFactoryRegistry :: getRegistry ().makeTest ()); testrunner.run (testresult); // output results in compiler-format CPPUNIT_NS :: CompilerOutputter compileroutputter (&collectedresults, std::cerr); compileroutputter.write (); // return 0 if tests were successful return collectedresults.wasSuccessful () ? 0 : 1; } |
CppUnit is very flexible, allowing lots of outputs for the tests, I’ll write another post about this soon, but the idea of this runner is to use the CppUnit test registry, what makes a lot easier to work, because you can forget about the runner, just write your tests and register it.
It is almost like the fileset you pass to the junit task of ANT but written in C++.
After the test runner is ready, you can start writing your tests.
C++ different from Java, you use 2 files for each class, one header and one implementation file.
Let’s start with the header …
mainTest.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #ifndef MAINTEST_H #define MAINTEST_H #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> #include "../src/HelloWorld.hpp" using namespace std; class MainTest : public CPPUNIT_NS :: TestFixture { CPPUNIT_TEST_SUITE (MainTest); CPPUNIT_TEST (testHello); CPPUNIT_TEST_SUITE_END (); public: void setUp (void); void tearDown (void); void testHello (void); private: HelloWorld *hello; }; CPPUNIT_TEST_SUITE_REGISTRATION (MainTest); #endif |
In this header we have a simple class declaration, extending TestFixture from the cpp unit namespace.
C++ is a static language without reflection, because of that CppUnit has the macros you can see in the top of the class declaration, you need one CPPUNIT_TEST line for each test method you write.
Ant the line: CPPUNIT_TEST_SUITE_REGISTRATION (MainTest);
Does the magic of test auto registration.
After this you can write your test as you do in Java:
mainTest.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include "mainTest.hpp" void MainTest::setUp(){ hello = new HelloWorld("Test"); } void MainTest::tearDown(){ delete hello; } void MainTest::testHello(){ string expected("Hello Test\n"); CPPUNIT_ASSERT_EQUAL(expected,hello->sayHello()); } |
As in JUnit we have a setUp and a tearDown method, this methods does exactly the same thing, they initialize/uninitialize the variables you will use in your test code, and the “testHello” method is our test.
the assertions in CppUnit are done using macros.
These are the available assertions:
A lot less assertion types than junit, but enough for working.
After the test done, now we just need to write our code!
HelloWord.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h> #include <iostream> #ifndef MAIN_HPP #define MAINHPP class HelloWorld{ private: std::string name; public: HelloWorld(char* name); std::string sayHello(); }; #endif |
and the implementation:
HelloWord.cpp
1 2 3 4 5 6 7 8 9 10 11 | #include "HelloWorld.hpp" HelloWorld::HelloWorld(char* name){ this->name = name; } std::string HelloWorld::sayHello(){ std::string result("Hello "); result = result + name + "\n"; return result; } |
To run the tests, you just need to type in the console:
make test
With all tests written and passing, the last step is the application runner (like the test runner).
main.cpp
1 2 3 4 5 6 7 8 | #include "HelloWorld.hpp" int main(int argc, char** argv){ if (argc >= 2) { HelloWorld* hello = new HelloWorld(argv[1]); std::cout << hello->sayHello(); delete hello; } } |
And you have your first test driven C++ application up and running!
PS.: if you are using my makefile, remember that application code is placed into src folder and test code is placed into test folder.
PS2.: the example was tested on linux with cpp unit installed using the package manager, if you want to install cpp unit from source, remember to update CFLAGS with the include path and LFLAGS with the library path. and if you are not using g++ as the compiler and linker, change the CC and LINKER variables with appropriated values.
If you enjoyed this post, make sure you subscribe to my RSS feed!