Sunday, December 1, 2013

C++ testing with t-unit

First of all: t-unit is released as open source under the MIT license, and the repository is available at: https://github.com/Lavesson/t-unit. There's also a quick start guide and a wiki available - For some background info, keep on reading.

The need for something simple

So, I wanted a simple unit test environment to test the logic in the 3D engine that I'm writing. What I really wanted was a unit test environment AND some way to output the results in an easy way.

I looked at a couple of different approaches, and most of the frameworks seems to be either, unportable, unmaintained, a hassle to build or simply just a piece of a much larger framework that you have no intention of using.

So my thoughts went to TAP (Test Anything Protocol). If I could just output a series of ok/not ok to a console, that would suffice for now. I hacked together a couple of header files which I could include in my project to make simple assertions and print ok/not ok with some debug info to standard output. I wrote this first implementation in a couple of days. It wasn't perfect, but it worked pretty well. I open-sourced the header files and called them tap-unit.

When I hit a large number of tests (or at least 100+), I realized that I wanted to condense the information in some way. I toyed with the idea of having an HTML report generator, as well as compress the test results into *just* showing the failing tests from a console. At first, I experimented with taking the TAP stream and piping it to a parser which produced either HTML or some nicely formatted output to the console. At this point I basically had my own assertion classes and macros for expanding test cases and fixtures, so TAP wasn't really central anymore, and constructing TAP just to parse it again just added another layer of indirection (without really gaining anything from it).

I rewrote the basic output generator and simply created instances of test cases as I ran them and just wrote a bunch of different report generators, such as TAP, HTML and console. This was much more modular. I changed the name from tap-unit to t-unit.

Introducing t-unit

First of all, t-unit is entirely header-based. There's no implementation files and nothing to build in advance (expect if you want to build the sample apps). You simply include t-unit.h and start writing tests. I wanted to use t-unit on some other projects as well, some of them on Linux, which means that I had to get it to compile on both MSVC and GCC. 

The tests are written and compiled as an executable file which is run manually. Tests are contained in fixtures the same way as many xUnit frameworks do (I happen to like this model of organizing tests). A typical test fixture with setup/teardown might look like:

FIXTURE(account_tests)
{
    account* sut;

    SETUP()
    {
        // Using a raw pointer. We need to clean this up after every test.
        sut = new enterprise_account(100);
    }

    TEST(starts_with_initial_sum)
    {
        assert.are_equal(100, sut->balance(), "expected correct balance at startup");
    }

    TEST(can_withdraw)
    {
        sut->withdraw(25);
        assert.are_equal(75, sut->balance(), "expected correct balance after withdrawing");
    }

    TEARDOWN()
    {
        // delete the account created in setup
        delete sut;
    }
}

Of course, the TEARDOWN macro could be avoided here by using a unique_ptr instead. I'm not going to get into the details here, but instead urge you to read the quick start guide or take a look at the examples in the wiki on Github.

Report generators

In the main method of the executable containing the file, you write some simple boilerplate code to wire everything up. For instance, to create a console report, you would write:
#include <t-unit.h>
#include <console-report.h>

int main()
{
    test_runner::run_all_tests();
    console_report cr;
    test_runner::create_report(cr);
    return 0;
}
You build and execute your project and should (hopefully) get a console report. A simple report example is shown below:


There's more report generators available (TAP and HTML). Again, for more information, consult the wiki. Most notably the generating reports section.

It would be nice to see other people use this, but even if I'm the only one doing so, I will still have gained something - A simple header-based testing utility which I can include in any project and just rebuild it as I write tests (or updates t-unit).

Oh: Bug reports and suggestions on improvements are, of course, welcome :)