Unit Testing

The Pt::Unit module provides a complete framework for effective unit testing. Simple tests are implemented by deriving from TestCase, multi-method suites by deriving from TestSuite. Setup and teardown of resources is provided by the TestFixture interface. Test conditions are verified through the Assertion class and the PT_UNIT_ASSERT macros. Execution order and data-driven repetition are controlled by a TestProtocol. Tests are auto-registered with RegisterTest and run by the Application class, which reports results through Reporter. A ready-made main() function is available by including TestMain.h.

Simple Test Cases

A TestCase can be used for simple tests that require a initialization and deinitialization of resources. The implementor is supposed to implement the abstract method 'test' and the methods 'setUp' and 'tearDown' for resource management. When the test is run, 'setUp' will be called first, then 'test' and finally 'tearDown'.

class MyTest : public Pt::Unit::TestCase
{
public:
MyTest()
: Pt::Unit::TestCase("MyTest")
{}
virtual void setUp()
{
// init resource
}
virtual void tearDown()
{
// release resource
}
void test()
{
// test code using a resource
}
};

Once the test is written it can be registered to an application by using the RegisterTest class template.

Test Suites

The TestSuite is used to implement protocol and data driven tests. It inherits its ability to register methods and properties from Reflectable. The implementor is supposed to write and register the required test methods on construction.

class MyTest : public Pt::Unit::TestSuite
{
public:
MyTest()
: Pt::Unit::TestSuite("MyTest")
{
this->registerMethod("Run", *this, &MyTest::Run);
}
void Run()
{
}
};

Once the test is written it can be registered to an application by using the RegisterTest class template. Include TestMain.h in exactly one source file to provide a main() function.

The default protocol will run each registered test method when the test is run. Before each test method setUp() is called and tearDown() after each test. The TestProtocol can be replaced with a customised one and reflection can be used to call any method multiple times with the required data.

Assertion Macros

Assertions are modeled as an exception type, which is thrown by Unit tests when an assertion has failed. This class implements std::exception and overrides std::exception::what() to return an error message Besides the error message, Assertions can provide information where the exception was raised in the source code through a SourceInfo object. It is recommended to use the PT_UNIT_ASSERT for easy creation from a source info object.

The following assertion macros are available:

void myTest()
{
PT_UNIT_ASSERT(5 + 5 == 10);
PT_UNIT_ASSERT_EQUAL(std::stoi("42"), 42);
PT_UNIT_ASSERT_NEAR(1.0 / 3.0, 0.333333);
PT_UNIT_ASSERT_THROW(std::stoi("abc"), std::exception);
PT_UNIT_ASSERT_NOTHROW(std::stoi("7"));
}

Protocol and Data-Driven Testing

This is the base class for protocols that can be used to run a test suite. The default implementation will simply run each registered test of the test suite without passing it any data. Implementors need to override the method TestProtocol::run to control the order and frequency of test method execution. This is useful to repeat tests, interleave them with waits, or implement stress-test patterns.

A protocol is assigned to a TestSuite through the constructor or via TestSuite::setProtocol().

class RetryProtocol : public Pt::Unit::TestProtocol
{
public:
void run(Pt::Unit::TestSuite& suite)
{
suite.runTest("Connect");
suite.runTest("Connect");
suite.runTest("Connect");
}
};

Test methods in a TestSuite can also take arguments for data-driven testing. The protocol calls TestSuite::runTest() with Pt::SerializationInfo objects to pass different data each time.

class MathProtocol : public Pt::Unit::TestProtocol
{
public:
void run(Pt::Unit::TestSuite& suite)
{
data[0] <<= 4;
data[1] <<= 9;
suite.runTest("MathTest::Addition", data, 2);
data[0] <<= 13;
data[1] <<= 2;
suite.runTest("MathTest::Addition", data, 2);
}
};

Automatic Test Registration

Tests can be registered easily with the RegisterTest<> class template to an Unit::Application at program initialisation. A typical example looks like this:

class MyTest : public Unit::TestCase
{ ... };
RegisterTest<MyTest> _registerMyTest;

The constructor of the RegisterTest class template will register an instance of its template parameter to the application.

Running Test Applications

The Application class serves as the environment for running unit tests. Tests are registered at program start using Pt::Unit::RegisterTest. A Reporter can be attached to process test events such as printing results to the console or writing log files.

The simplest way to provide a main() function is to include TestMain.h in exactly one source file of the test executable. It creates an Application, attaches a BriefReporter for console output and supports the following command line arguments:

  • -h — prints a list of all registered tests.
  • -t <name> — runs only the test with the given name.
  • -f <file> — additionally writes test output to a log file.

When executed without arguments, all registered tests are run. The exit code equals the number of errors, so a successful run returns 0.

For more control over reporter setup or test execution, a custom main() function can be written instead of including TestMain.h.

#include <Pt/Unit/Application.h>
#include <Pt/Unit/Reporter.h>
int main()
{
Pt::Unit::BriefReporter reporter;
app.attachReporter(reporter);
app.run();
return app.errors() > 0 ? 1 : 0;
}

Reporting Test Results

This class is the base class for all reporters for test events. It lets the implementor override several virtual methods that are called on perticular events during the test. Reporters can be made to print information to the console or write XML logs.

Core module.
Definition: pt-gfx-images.dox:16
virtual void run(TestSuite &test)
Executes the protocol.
Protocol for test suites.
Definition: TestProtocol.h:93
#define PT_UNIT_ASSERT(cond)
Asserts that a condition is true.
Definition: Assertion.h:118
virtual void test()=0
Implements the actual test procedure When the test is run, this method will be called after setUp and...
#define PT_UNIT_ASSERT_NEAR(value1, value2)
Asserts that two floating-point values are approximately equal.
Definition: Assertion.h:190
#define PT_UNIT_ASSERT_EQUAL(value1, value2)
Asserts that two values are equal.
Definition: Assertion.h:169
unsigned errors() const
Returns the number of errors which occured during a run.
Definition: Application.h:132
Protocol and data driven testing.
Definition: TestSuite.h:84
#define PT_UNIT_ASSERT_NOTHROW(cond)
Asserts that an expression does not throw.
Definition: Assertion.h:245
Single test with setup and teardown.
Definition: TestCase.h:82
void runTest(const std::string &name, const SerializationInfo *args=0, std::size_t argCount=0)
Runs a registered test.
virtual void tearDown()
Clean up after the test run.
Registers tests to an application.
Definition: RegisterTest.h:59
void run(const std::string &testName)
Run test by name.
Run registered tests.
Definition: Application.h:81
Represents arbitrary types during serialization.
Definition: SerializationInfo.h:59
void attachReporter(Reporter &r)
Add reporter for test events.
virtual void setUp()
Set up conText before running a test.
#define PT_UNIT_ASSERT_THROW(cond, EX)
Asserts that an expression throws a specific exception.
Definition: Assertion.h:219