Wednesday, August 15, 2007

Windows Mobile Native Unit Testing

When applied with discipline and care, unit testing can be a powerful weapon in a software developer's arsenal. It allows a developer to garner feedback on functional correctness as well as refactor an existing codebase with security and confidence. Additional benefits of unit testing can be greater modularity, looser coupling, and an easier ability to measure whether software is meeting functional requirements.

There is a wealth of information on the Internet regarding unit testing and unit testing frameworks, but very few items are Windows Mobile specific. With the .NET Compact Framework, unit testing can currently be done with the Mobile Client Software Factory, released in mid-2006. And with the upcoming release of Visual Studio 2008 and the .NET Compact Framework version 3.5, unit tests are integrated into Visual Studio for managed mobile development.

The managed side is covered pretty well at this point (or will be when Visual Studio 2008 comes out), but what about the native side? What is available for C++ developers on Windows Mobile to use? One answer is CppUnitLite.

CppUnitLite was written by Michael C. Feathers, author of Working Effectively with Legacy Code. Feathers was also the original author of CppUnit and developed CppUnitLite when he realized that CppUnit could have been "smaller, easier to use, and far more portable if it used some C idioms and only a bare subset of the C++ language." [Feathers p48]

Here are steps to setup CppUnitLite for use with Windows Mobile.

1. Download CppUnitLite from the Object Mentor website.

2. Extract the following files from the om/CppUnitLite section of the zip file:

  • Failure.h
  • SimpleString.h
  • Test.h
  • TestHarness.h
  • TestRegistry.h
  • TestResult.h
  • Failure.cpp
  • SimpleString.cpp
  • Test.cpp
  • TestRegistry.cpp
  • TestResult.cpp

(In this demo, these files have been extracted to C:\CppUnitLite)

3. Open the Solution which requires unit tests.(In this demo, the solution is named StackExample. This solution contains one project, Stack, which contains two files, Stack.h and Stack.cpp. Image 1 contains a screenshot of the solution loaded in the Solution Explorer. Stack.h is displayed in Code Listing 1. Stack.cpp is displayed in Code Listing 2.)

Image 1 - StackExample solution in Solution Explorer

 

Code Listing 1 - Stack.h

#ifndef Stack_h
#define Stack_h

#if (_MSC_VER > 1000)
#pragma once
#endif

/*!
* \brief
* This class is a stack which holds integer
* values.
*
* The size of the stack is determined at time
* of construction. A stack uses the last-in
* first-out approach.
*/
class Stack
{
public:
Stack(int maxSize);
virtual ~Stack();

bool IsEmpty() const;
void Push(int item);
int Pop();
private:
int* m_items;
int m_currentSize;
int m_maxSize;
};

#endif

Code Listing 2 - Stack.cpp

#include "Stack.h"
#include <stdexcept>

using namespace std;

/*!
* \brief
* This method is the constructor for the Stack class.
*
* \param maxSize
* This parameter is the maximum number of integers
* the stack can hold.
*/
Stack::Stack(int maxSize)
{
m_maxSize = maxSize;
m_items = new int[maxSize];
m_currentSize = 0;
}

/*!
* \brief
* This method is the destructor for the Stack class.
*/
Stack::~Stack()
{
delete[] m_items;
}

/*!
* \brief
* This function calculates whether or not the
* stack is currently empty.
*
* \returns
* This function returns true if the stack is
* empty, or false otherwise.
*/
bool Stack::IsEmpty() const
{
return (m_currentSize == 0);
}

/*!
* \brief
* This function adds an integer value to the stack.
*
* \param item
* This parameter is the integer value to add to the stack.
*
* \throws exception
* This function throws an exception if a value is pushed
* when the stack is already full.
*/
void Stack::Push(int item)
{
if (m_currentSize >= m_maxSize)
{
throw exception("Stack is full.");
}
else
{
m_items[m_currentSize++] = item;
}
}

/*!
* \brief
* This function removes an integer value from the stack.
*
* \returns
* This function returns the last integer value added to
* the stack and removes it.
*
* \throws exception
* This function throws an exception if this function is
* called when the stack is empty.
*/
int Stack::Pop()
{
if (IsEmpty())
{
throw exception("Stack is empty.");
}
else
{
return m_items[--m_currentSize];
}
}

4. Add a new Win32 Smart Device Project to the solution named CppUnitLite and place it in the directory where the CppUnitLite source code was extracted in Step 2.

Image 2 - Add New Project Wizard

5. Add all Windows Mobile platform SDKs to the new project.

Image 3 - Win32 Smart Device Project Wizard (Platform)

6. Set the Application type to Static Library and turn off Precompiled header in the Additional options section.

Image 4 - Win32 Smart Device Project Wizard (Application Settings)

7. Move the project file and the readme.txt file into the main directory where the CppUnitLite files are stored. The project needs to be removed and readded to the solution within Visual Studio.

Image 5 - Directory listing of CppUnitLite files.

8. Using the Add->Existing Item... mechanism, add the header files (*.h) to the Header files section and add the source files (*.cpp) to the Source files section.

Image 6 - CppUnitLite project with all header and source files added

This walkthrough assumes that the code to be tested is in a static library (*.lib). In the example, Stack.h and Stack.cpp are in a static library project named Stack.

9. Add a new project to the solution and name it LibraryName.Tests. In the example, the project is named Stack.Tests.

10. Right-click on the project and choose properties. In the Common Properties->References section add the other two projects as references by using the Add New Reference button.

Image 7 - References added to the project properties.

11. In the Configuration Properties->C/C++->General section is an option named Additional Include Directories. Add the directories which contain the header files of the classes to be tested as well as the directory which contains the CppUnitLite header files. In the example, these two directories are ..\Stack and C:\CppUnitLite.

Image 8 - Additional Include Directories added to the project properties.

12. Add a .cpp file with a command line main method for Windows Mobile in it. This file should include TestHarness.h from CppUnitLite. In the main method, create a TestResult variable and pass it into TestRegistry::runAllTests as done in the StackMain.cpp example in Code Listing 3.

Code Listing 3 - StackMain.cpp

#include "TestHarness.h"
#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
TestResult tr;
TestRegistry::runAllTests(tr);

return 0;
}

13. Add a file named corresponding to the class to be tested. In the example, the Stack class is being tested, so the file is named StackTest.cpp. The code for this file is listed in Code Listing 4.

Code Listing 4 - StackTest.cpp

#include "TestHarness.h"
#include "Stack.h"
#include <string>

TEST( MultiPushPop, Stack )
{
Stack s(10);
s.Push(14);
LONGS_EQUAL(14, s.Pop());
s.Push(12);
s.Push(18);
s.Push(27);
LONGS_EQUAL(27, s.Pop());
s.Push(38);
s.Push(2);
s.Push(12);
LONGS_EQUAL(12, s.Pop());
LONGS_EQUAL( 2, s.Pop());
LONGS_EQUAL(38, s.Pop());
LONGS_EQUAL(18, s.Pop());
LONGS_EQUAL(12, s.Pop());
CHECK(s.IsEmpty());
}

TEST( PushExceedsMax, Stack )
{
Stack s(5);
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
s.Push(5);
try
{
s.Push(6);
FAIL("Push should have exceeded max stack size");
}
catch (std::exception ex)
{
// Expected behavior
}
}

TEST( PushPop, Stack )
{
Stack s(10);
s.Push(27000);
CHECK(!s.IsEmpty());
int value = s.Pop();
LONGS_EQUAL(27000, value);
CHECK(s.IsEmpty());
}

TEST( PopEmpty, Stack )
{
Stack s(10);
try
{
// This test should throw an exception
int value = s.Pop();
FAIL("Popping an empty stack should throw an exception");
}
catch (std::exception ex)
{
// Expected behavior
}
}

TEST( IsEmpty, Stack )
{
Stack s(10);
CHECK(s.IsEmpty());
}

TEST( Creation, Stack )
{
Stack s(10);
}

13. Set the .Tests project (Stack.Tests) as the startup project by right-clicking on the project and choosing Set as Startup Project.

14. Build all the projects.

15. Make sure to choose the same emulator or device for all three projects otherwise multiple emulator windows may pop up.

16. Run the project. Hopefully, a message stating "There were no test failures" is then displayed in the Output window as shown in Image 9.

Image 9 - Successful Test Run

Aside from the examples that come with CppUnitLite, there are additional examples within Working Effectively with Legacy Code.

7 comments:

Unknown said...

Super!
Thank you for this post.

YouthOfUnity said...

Ryan,
Step 9 is not making sense to me, plus it would be great, if you can discuss the actual tests as well.

Thanks,
-Kamal.

Poornima Biradar said...

This post is very helpful for me ...
We don't get much information about "native code development on Windows mobile" on net. Thanks a lot. its really very helpful post for developers.

YouthOfUnity said...

Ryan,
I have implemented all the steps , and finally it WORKS !

Thanks,

The only issue i am having is that i am not seeing the line
"There are no test failures"
in stdout

any ideas?

Poornima Biradar said...

kahmed,

Main thing is Windows Mobile
doesnt have Stdin and
Stdout. CppUnitLite is using Std err for outputting . if you route your std err to visual studio out put window you can see the cppunitlite output. or you can overload std err to write to a file. hope this information helped you.

Thanks,
Poornima

Unknown said...

I am currently working on an AEP (automated error prevention) technology called C++test, which automatically generates unit test suites for Windows Mobile native applications. The test cases are also in Cppunit format.

I can even upload the test suites to my target WM Device, run the tests on the device and have the results sent back to my host machine for analysis.

It is an excellant solution for Windows Mobile Native unit testing.

Happy to give anyone an introduction.

YouthOfUnity said...

Anthony,

I would really appreciate if you could give me an introduction on C++test, namely:
1. Environment setup and pre requisites
2. Device Setup ( USB port.. etc )
3. Generating Unit Tests
4. Sample Unit Test
5. Running the test on device
6. Results

Thanks,
-Kamal.