I think I don't have to mention the value of automated testing. By adding some test cases to KBruch I was able to identify 3 major bugs. So regression testing is really worth the effort.
Please also take a look at the Boost Test online documentation. There you will find a description of all library functions I use.
First of all I have created a new directory called testcases where I have put the test cases into. Now I added symbolic links from all classes I like to test to the testcases directory. I like to provide a small script for this purpose. Nevertheless, check the results afterwards. And of course this script will fail, if the cpp/h files are located in subdirectories under the ../src directory. But here we go:
#!/bin/bash # creates symlinks of all cpp/h files in ../src to this directory # # Author: Sebastian Stein <seb.kde-AT-hpfsc.de> # save the current directory # current=`pwd` # delete all symbolic links # rm `find . -type l` # now go to the source directory # cd ../src echo "Creating symlinks to the source files..." # make a symlink from every *.cpp file to this directory # for a in `ls *.cpp` do ln -s ../src/$a ../testcases/$a done # make a symlink from every *.h file to this directory # for a in `ls *.h` do ln -s ../src/$a ../testcases/$a done # and back to the testcases directory # cd $currentNow, you also have to edit the Makefiles in the base directory of your project. Just add the new testcases directory to the "SUBDIRS" variable in the Makefile.am file.
The testcases subdirectory also needs a Makefile.am. This one is a little bit harder. Let's take a look at it:
check_PROGRAMS = kbruch_test kbruch_test_SOURCES = task.cpp ratio.cpp ratio_test.cpp primenumber.cpp primenumber_test.cpp kbruch_test.cpp kbruch_test_LDADD = -lboost_unit_test_framework-gcc $(LIB_KDEUI) # the library search path. kbruch_test_LDFLAGS = $(all_libraries) $(KDE_RPATH) EXTRA_DIST = primenumber.cpp primenumber.h ratio.cpp ratio.h task.cpp task.h primenumber_test.cpp ratio_test.cpp kbruch_test.cpp # set the include path for X, qt and KDE INCLUDES= $(all_includes)Before I begin explaining the file please note, I'm not an automake/autoconf guru, so I might just state nonsense... In the first line we add the target. The test will be a program which we have to call to do the tests. This program will be called kbruch_test and can be build by calling make check. Of course we have to say which are the source files of this program. Here you should add all classes you wish to test (e.g. ratio.cpp), the actual test classes (e.g. ratio_test.cpp) and of course the file containing the "main" function of your test program (here kbruch_test.cpp).
All other lines are as usual. The only difference can be found in the LDADD line. You have to add the Boost unit testing framework library here. Unfortunally I can't say how this line must look like for your system. Because the library name contains some system specific information like compiler and multi threaded version or not. Thomas Porschberg has created an automake macro for this purpose. Checkout his great work!
Now we have set up the automake system. Don't forget to run make -f Makefile.cvs and sh configure --enable-debug=yes in the module again. You are still able to build your application by calling make, but you can now also build the test cases by calling make check. After the test cases were built, you have to call the test program with testcases/kbruch_test. But let's go on how to write test classes.
Now let's take a look at the primenumber_test class:
// for BOOST testing #include <boost/test/unit_test.hpp> using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; // the class to be tested #include "primenumber.h" class primenumber_test { public: // constructor primenumber_test() { } /** test the prime number algorithm */ void test_isPrimeNumber() { BOOST_REQUIRE(m_primenumber.isPrimeNumber(0) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(2) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(3) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(4) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(5) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(6) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(7) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(8) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(9) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(23) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(9) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(9) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(6) == 0); BOOST_REQUIRE(m_primenumber.isPrimeNumber(101) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(323) == 0); // 17 * 19 BOOST_REQUIRE(m_primenumber.isPrimeNumber(1001) == 0); // 7 * 143 BOOST_REQUIRE(m_primenumber.isPrimeNumber(1002) == 0); // 2 * 501 BOOST_REQUIRE(m_primenumber.isPrimeNumber(3) == 1); BOOST_REQUIRE(m_primenumber.isPrimeNumber(2) == 1); } /** test the get_first() function */ void test_get_first() { BOOST_REQUIRE(m_primenumber.get_first() == 2); } /** test the move and get functions */ void test_move_get_func() { m_primenumber.move_first(); BOOST_REQUIRE(m_primenumber.get_current() == 2); BOOST_REQUIRE(m_primenumber.get_next() == 3); m_primenumber.move_forward(); BOOST_REQUIRE(m_primenumber.get_current() == 5); m_primenumber.move_back(); BOOST_REQUIRE(m_primenumber.get_current() == 3); unsigned int tmp = m_primenumber.get_last(); m_primenumber.move_last(); BOOST_REQUIRE(m_primenumber.get_current() == tmp); m_primenumber.move_forward(); BOOST_REQUIRE(m_primenumber.get_last() != tmp); } private: // instance of primenumber primenumber m_primenumber; }; class primenumber_test_suite : public test_suite { public: primenumber_test_suite() : test_suite("primenumber_test_suite") { // create an instance of the test cases class boost::shared_ptr<primenumber_test> instance(new primenumber_test()); // create the test cases test_case* isPrimeNumber_test_case = BOOST_CLASS_TEST_CASE( &primenumber_test::test_isPrimeNumber, instance ); test_case* get_first_test_case = BOOST_CLASS_TEST_CASE( &primenumber_test::test_get_first, instance ); test_case* move_get_func_test_case = BOOST_CLASS_TEST_CASE( &primenumber_test::test_move_get_func, instance ); // add the test cases to the test suite add(isPrimeNumber_test_case); add(get_first_test_case); add(move_get_func_test_case); } };As you can see, there are actually 2 classes in this file. Class primenumber_test is the real testing class. It defines a test function for each to be tested public function of class primenumber. I'm not sure if you only can test public functions, but I think it is the right way to do it. You are not interested in the details how the function works. The class is just a black box for you. Just a stable interface (public functions) is important, so it is enough (and already a lot of work) to test those functions.
As you can see, there is the BOOST_REQUIRE() stuff. Each line is a test. Each test function is called a test case. A test case can consist of several tests. The test cases are combined into a test suite. This combination is done in the second class called primenumber_test_suite. You don't have to think much about this class. Just do it that way. It is always the same.
I like to mention that it is sometimes a little bit difficult to find good test cases. But I am pretty sure there are web pages out there describing how to do this. So I won't repeat the ideas here again.
// for BOOST testing #include <boost/test/unit_test.hpp> using boost::unit_test_framework::test_suite; // the test classes #include "primenumber_test.cpp" #include "ratio_test.cpp" // test program entry point test_suite* init_unit_test_suite(int /* argc */, char** /* argv */) { // create the top test suite std::auto_ptr<test_suite> top_test_suite(BOOST_TEST_SUITE("Master test suite")); // add test suites to the top test suite top_test_suite->add(new primenumber_test_suite()); top_test_suite->add(new ratio_test_suite()); return top_test_suite.release(); }The first thing you should notice is, that the program entry function is not called main. It is instead called init_unit_test_suite. In this function the 2 test suites are added and the test is executed. If a test fails, the program will show which test failed and stop the execution. So this is very easy as well.
Please feel free to send me any hints, advices or corrections regarding this small tutorial!