2012-05-24

Set Eclipse Username

Eclipse has ${user} variable which can be used in code template. Normally, it is the name of the operating system user. There are ways to change this to any string you want:

1. From command line

eclipse -vmargs -Duser.name="Aric Wang"

2. From Eclipse Configuration File

Edit 'eclipse.ini' in the installation folder. After line '-vmargs', add a new line like

-Duser.name=Aric Wang

Restart eclipse.

2012-05-23

C++ Unit Test Framework with CppUnit


Introduction

This unit test framework is for all the libraries and applications built within the Application repository. In theory, every class of those libraries and applications should be covered in the unit test.

In order to keep the production code clean, an important rule is that there should be no test related code in the production code. All the unit test code should be separate from the production code, and they should not be built into the production binaries in any case.

CppUnit is used for unit test framework. Poco comes with CppUnit. To build CppUnit library, Poco has to be configured without '--no-tests'.

Below sections will explain the basic concepts of CppUnit and how to use this framework.

CppUnit Concepts


CppUnit has a couple of concepts to organize the unit test, and three of those concepts are needed to understand the Apple AP unit test framework.

Fixture


Fixture is normally a class derived from CppUnit::TestCase. Fixture class is the container of test cases. Each test case is a member function of fixture class. A fixture class can have more than one test cases.

In order to share some common setup between multiple test cases, fixture class can have member variables. Fixture class can override setUp() function to initialize the test environment. tearDown() function can be used to clean up the test environment after execution of test cases.

Test Case


A test case is a public member function of a fixture class. A fixture class can have more than one test cases. Each test case is responsible for testing some features of the object under testing (class or function).

A test case function has a prototype like: void TestXyz(). It normally does some test work of the function Xyz() and check the result with some macros defined by CppUnit::TestCase, i.e. assert(condition). See CppUnit::TestCase for more details.

Test Suite


Test suite is a container of multiple test cases or test suites so that all test cases under it can run at once.

Unit Test Framework

Build Unit Test Suites

All the unit test cases and test suites are built into one application in order to keep the testing code out of the production code. At the top level of Application repository, execute below command to build the unit test:
make unittest
The unit test application is located in the 'Build/$(ARCH)' folder and named 'UnitTest'.

'unittest' is also included in the 'all' target. So another way to build the unit test suites is:
make all

Run Unit Test Suites

To execute the UnitTest application, make sure all related libraries are installed properly. For example, the Poco libraries, including CppUnit library, should be installed properly. If the shared libraries of the Application repository are used by the UnitTest application, they should also be installed properly. If these libraries are not installed, an alternative way is to use LD_LIBRARY_PATH at the command line to specify the pathes of those libraries.

For example, to execute the UnitTest application on x86 Linux platform:
cd Application/Build/i386
LD_LIBRARY_PATH=../../../Poco/poco-1.4.3p1-all/lib/Linux/i686:. ./UnitTest -all
If all test cases succeed, it will list all the test cases, and followed with 'OK (x tests)' at last where x is the number of test cases executed. This is an example:
testzHalt:
testzFive: 
OK (2 tests)
If any test case fails, it will be marked after the name of the test case. A summary of the test result is followed, and then the list of the failures. This is an example:
testzHalt: FAILURE
testzFive:
!!!FAILURES!!!
Runs: 2   Failures: 1   Errors: 0
There was 1 failure:
 1: N7CppUnit10TestCallerI8TestBaseEE.testzHalt
    "false"
    in "/home/one/apple/Application/Test/UnitTest/Lib/Base/TestBase.cpp", line 51
The result code of the UnitTest application can also be used to tell success or failure. If all test cases are successful, the return code will be 0. If any test cases fails, the return code will not be 0.

The UnitTest can run in a couple of formats:
# List all the test suites and test cases in a tree format
UnitTest -print
# Execute all test cases
UnitTest -all  
# Execute one or more test cases.
UnitTest <testcase name> ...   
It is quite flexible to execute a single test case, or a single test suite, or a sub-tree of all the test cases, or even all the test cases.

Basic Classes


The unit test framework mainly deals with two kind of classes: fixture class and test suite class.

Fixture class implements a test fixture, including: setUp(), tearDown(), test cases, and a test suite containing all the test cases. Fixture class is derived from CppUnit::TestCase.

Test suite class is a simple class, which includes a test suite. It is used as a container to organize the fixture classes. Each fixture class's test suite is added to a test suite class, and the test suite class is added to a higher level test suite class. The top level test suite class is TestSuiteTop. All the test suites are organized like a tree. The leave nodes are test cases.

Fixture Class Example

The header file:
#include "CppUnit/TestCase.h" // Base class of fixture class
#include "Base/Base.h"        // The class under test
// Fixture Class Example.
// This class can have member variables in order to set up a environment for
// executing the test cases. In this example, there's no such variables.
//
// This fixture class contains one test case function 'void TestzHalt()', and
// one test suite 'static CppUnit::Test* suite()'.
//
class TestBase: public CppUnit::TestCase
{
public:
    // Return test suite includes all the test cases of this fixture class
    static CppUnit::Test* suite();  
    TestBase(const std::string& name);
    virtual ~TestBase();
    void setUp();       // Set up the environment for executing the test cases
    void tearDown();    // Clean up after execution of test cases
    void TestzHalt();   // Test case for testing Base::zHatl().
                        // More test cases can be added to this fixture.
private:
};
The soucre file:
#include "CppUnit/TestCaller.h"
#include "CppUnit/TestSuite.h"
#include "Base/TestBase.h"
CppUnit::Test* TestBase::suite()
{
    // Create the test suite, and give it a name "TestBase"
    CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("TestBase");
    // Add all test cases to the test suite.
    // pSuite      The test suite object
    // TestBase    The name of the fixture class
    // TestzHalt   The name of the test case function
    CppUnit_addTest(pSuite, TestBase, TestzHalt);
    return pSuite;
}
TestBase::TestBase(const std::string& name): CppUnit::TestCase(name)
{
}

TestBase::~TestBase()
{
}
void TestBase::setUp()
{
    // Set up the environemtn if necessary
}
void TestBase::tearDown()
{
    // clean up the environment after exectuing the test cases.
}
void TestBase::TestzHalt()
{
    // Do some test work on function zHatl().
 
    // Check the result of the test. Normally should check a condition.
    assert(condition);
}
Test Suite Class Example

The header file:
#include "CppUnit/TestSuite.h"
// Test Suite Class Example.
// Test suite class has no test cases, no setUp(), no tearDown(), no member
// variables. It is a container to organize fixture classes.
//
class TestSuiteBase
{
public:
    static CppUnit::Test* suite();    // Return test suite object
};
The source file:
#include "Base/TestSuiteBase.h"
#include "Base/TestBase.h"
CppUnit::Test* TestSuiteBase::suite()
{
    // Create the test suite, and give it name "TestSuiteBase"
    CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("TestSuiteBase");
    // Add fixture class as child test suite. More fixture class can be added.
    // TestBase     The fixture class to be added
    pSuite->addTest(TestBase::suite());
    return pSuite;
}

Code Location

All the unit test code is kept out of the production code, and located under 'Test/UnitTest' folder. Under that folder, similar folder structure is duplicated for source code under 'Lib' and 'App'. Difference is that there's no separate 'Inc' folder for header files. Both header file and source file go to the same folder.

Naming Convention

In order to easily identify the test related code, all the test related files, classes, and test case functions are named in a particular way:

All test suite files are named like TestSuiteModuleName.h/cpp, where 'ModuleName' is the name of the module under test.
All test suite classes are named like TestSuiteModuleName, where 'ModuleName' is the name of the module under test.
All fixture files are named like TestClassName.h/cpp, where 'ClassName' is the name of the class under test.
All fixture classes are named like TestClassName, where 'ClassName' is the name of the class under test.
All test case functions are named like TestFunctionName(), where 'FunctionName' is the name of the function under test.

Test Case Tree

All the test cases are organized into many test suites like a tree. The top level test suites is 'TestSuiteTop'. Each module has a test suite class as a container of all the test suites from that module. Each class has a fixture which includes one or more test cases and a test suite containing all the test cases.

For example, Access application has a 'TestSuiteAccess' which contains all the test suites of its sub-modules. The sub module of Access, i.e. Database, each has a test suite, like TestSuiteDatabase, which contains all the fixtures of its classes. For each class of submodule Database, i.e. class Cardholder, there is a fixture for it, like TestCardholder. The fixture TestCardholder implements all the test cases for class Cardholder, and it also has a test suite to contain all the test cases.

Put the code and test case organization in a tree view:

UnitTest
    |- TestSuiteTop.h/cpp                     - Top level test suite
    |- App
    |   |- Access
    |        |- TestSuiteAccess.h/cpp         - Module test suite
    |        |- Module1
    |        |   |- TestSuiteModule1.h/cpp    - Module test suite
    |        |   |- TestClass1.h/cpp          - Class fixture
    |        |   |- TestClass2.h/cpp          - Class fixture
    |        |- Module2
    |            |- TestSuiteModule2.h/cpp    - Module test suite
    |            |- TestClass3.h/cpp          - Class fixture
    |            |- TestClass4.h/cpp          - Class fixture
    |- Lib
        |- Base
            |- TestSuiteBase.h/cpp            - Module test suite
            |- TestClass5.h/cpp               - Class fixture
            |- TestClass6.h/cpp               - Class fiture

Add Test Case for New Code

If new module, class, or function is added to the production code, you may want to add test cases for it too. There're a couple of things to do:


  1. Create new folder under 'Test/UnitTest' accordingly if necessary.
  2. Create module test suite in the new folder if necessary.
  3. Add the module test suite to its parent module's test suite if necessary.
  4. Create class fixture in the folder for new classes if necessary.
  5. Implement the fixture's test suite if necessary.
  6. Add the fixture's test suite to the module's test suite if necessary.
  7. Implement the test case functions for the new code.

The UnitTest application has to link with the classes under testing. Normally, those classes are out of the 'Test/UnitTest' folder so that they can't be picked up by the makefile automatically. In order to include those classes into the UnitTest application, 'Test/UnitTest/makefile' has to be changed to pick up the code outside 'Test/UnitTest' folder. EXTRA_MODULE can be used for this purpose. For example, to include 'Lib/Base' and 'App/Access/Abc', it has to be included in EXTRA_MODULE like this:

# Extra modules, source code outside of this module
EXTRA_MODULES := \
    $(TOP_DIR)/Lib/Base \
    $(TOP_DIR)/App/Access/Abc

Make sure that the applications main() function is not linked to UnitTest application.