Introduction
First of all, please forgive me for my bad English if it annoys you. Few months ago, I came to know about CPP Unit which is a unit testing framework for C++ application. I am a fresh guy out of college last year and was unaware about testing frameworks, so it was a very good experience for me to work with CPP Unit. As I was also programming on Windows CE, there was a need to write test cases for those applications also. But the problem with CPP Unit is that I was not able to compile for Windows CE as it used some features of C++ which are not supported by eVC++ compiler. I tried to search a similar test framework for Windows CE but I didn't find it. So after prolonged searching, I thought to write my own unit testing framework for Windows CE based applications. And this is what I came up with and I want to share with all.
This is a unit testing framework for applications written for Windows CE in particular. It provides GUI to select and run test cases, and to show results. It also stores the results of tests run to a file, if user wants.
Background
Programming in C++ for WinCE is very restrictive. It does not support templates, RTTI and other stuff of C++. CPP Unit uses templates and RTTI, but in eVC++ it's not available. That's why I have used lots of member function pointers and call backs to implement it.
Using the Frame Work
Compile the WCEUnit project to make WCEUnit.dll file for a particular platform. In order to use the testing framework, the test case project must be linked with the WCEUnit.dll. Write your test cases and link the project to WCEUnit.dll. Following topics discuss how to write test cases.
Writing Test Cases
When you write a test class, it should be derived from CTestSuite
class defined in the framework. The CTestSuite
class holds all the information about the test cases written for that particular suite. The individual test cases should be added to the test suite as show in the following example. The macros which are used in the following example are defined in the WCEMacros.h header file. These macros wrap the test case addition mechanism. The below code shows a test suite class written for testing CComplex
class. The CComplex
class is very simple, and does primary implementation of complex numbers, and I will not go into the details of that one.
#ifndef __TEST_COMPLEX_INCLUDED__
#define __TEST_COMPLEX_INCLUDED__
#include "..\WCEUnit\TestSuite.h"
#include "..\WCEUnit\WCEUnitMacros.h"
#include "Complex.h"
class CTestComplex : public CTestSuite
{
public:
WCEUNIT_TESTSUITE_INIT(CTestComplex);
WCEUNIT_ADD_TESTCASE(testAdd);
WCEUNIT_ADD_TESTCASE(testEqual);
WCEUNIT_ADD_TESTCASE_EXCEPTION(testExcp, CUserException);
WCEUNIT_TESTSUITE_END();
public:
void testAdd();
void testEqual();
void testException();
void CleanUpTest();
void InitializeTest();
private:
CComplex* m_p1;
Ccomples* m_p2;
CComplex* m_p3;
};
#endif
Macro description
WCEUNIT_TESTSUITE_INIT(testSuite)
Pass test suite class name to it. This function defines the starting of a CreateTestSuite()
function. This function is defined as pure virtual in the CTestSuite
class and is used to create the test case lists.
WCEUNIT_ADD_TESTCASE(testCase)
This macro adds the code to the CreateTestSuite()
function to add a test case function.
WCEUNIT_ADD_TESTCASE_EXCEPTION(testCase, testException)
This macro is used to add a special type of test case function which should be tested for a particular type of exception. If the test case does not throw the exception then it is considered to fail.
WCEUNIT_TESTSUITE_END()
This macro ends the CreteTestSuite()
function.
You can see functions InitializeTest()
and CleanUpTest()
in the definition of the class. These functions are called before and after every test case execution. From the UI, you can select the behavior about these functions, i.e., whether you want those functions to be called for every test case or only once for all the test cases for a particular test suite class. You can specify your choice by checking or un-checking the Init Once check box on the main dialog.
Writing Test Conditions
While writing test cases, use macros defined in the WCEUnitMacros.h for writing the test conditions. Following are the macros used to write test conditions:
WCEUNIT_ASSERT(condition)
If the condition
is evaluated to false then it asserts and marks the test as failed.
WCEUNIT_ASSERT_FAIL(condition)
This is reverse of above macro. If you want that the condition must return false then use this macro to test the condition.
WCEUNIT_ASSERT_EQUALS(testValue, compValue)
This macro asserts if the testValue
and compValue
are not equal. If the two values are not equal then it marks the test case as failed. If you are using some user defined class objects to compare then the class must have '==
' operator overloaded.
WCEUNIT_DOUBLE_EQUALS(testValue, compValue)
This macro is similar to the above one but it assumes that the testValue
and compValue
are convertible to a double
value. It type casts the results into a double
value and then compares.
WCEUNIT_DOUBLE_BETWEEN(testValue, minValue, maxValue)
This macro tests weather the testValue
is less than maxValue
and greater than minValue
. It is assumed that the three values of expressions are evaluated to double
. The following code shows the usage of the above macros.
Following code shows the use of above macros in test cases for CComplex
class.
void CTestComplex::testAdd()
{
CComplex temp;
temp = *m_p1 + *m_p2;
WCEUNIT_ASSERT(CComplex(30, 70) == temp);
}
void CTestComplex::testEqual()
{
CComplex temp2; WCEUNIT_ASSERT_FAIL(temp2 == *m_p3);
CComplex temp1; temp1 = *m_p1 + *m_p2;
WCEUNIT_ASSERT_EQUALS(temp1, *m_p3);
WCEUNIT_DOUBLE_EQUALS(temp1.GetReal(), m_p3->GetReal());
WCEUNIT_DOUBLE_EQUALS(temp1.GetImaginary(), m_p3->GetImaginary());
}
void CTestComplex::testException()
{
CUserException e;
THROW(&e);
}
void CTestComplex::InitializeTest()
{
m_p1 = new CComplex(10, 20);
m_p2 = new CComplex(20, 50);
m_p3 = new CComplex(30, 70);
}
void CTestComplex::CleanUpTest()
{
delete m_p1;
delete m_p2;
delete m_p3;
}
Running Test Cases
The CTestRunner
class is responsible for running the test cases and getting the test results. To run the test cases you have written, create an instance of CTestRunner
class. Also create the instance of all the test suite classes, and pass the address of each in the AddTestSuite()
function of CTestRunner
class. Then call Start()
function of CTestRunner
. The following code shows how to run test cases:
BOOL CTestComplexApp::InitInstance()
{
CTestRunner obj;
CTestComplex test1;
obj.AddTestSuite(&test1);
obj.Start();
return FALSE;
}
The GUI
The above image shows the main dialog. To select a test case to run, click the Select button, a new dialog will appear with the tree view of the test cases written by you. You can either select single test case or a test suite or All Tests. If you want to run all the test cases for a particular test suite then select that test suite, or if you want to run all the test cases of all the test suites then select the root node All Tests. The check box Init once is given for the option when the InitializeTest()
and CleanUpTest()
will be called. If it is checked then for each test suite, the IntializeTest()
and CleanUpTest()
is called only once respectively, before and after all the test cases are executed. If it is unchecked then for each and every test case, these two functions are called. Click the Run button to run the selected test cases. The results are displayed in the list box. If you want to see more information then just click the result item, you will be shown the detailed result. If you want to store the test results in a text file then check the Make log check box, a file \\WCEUnitLog.txt is created with the detailed test result information.
Points of Interest
During the development of this project, I learned a lot about the pointer to member functions in C++. They are amazing things and do a lot more than what you can imagine. May be next time, I will try to post some thing about pointer to member functions.