Wiki

Clone wiki

xUnit++ / Tests

Tests

There are two types of tests: Facts and Theories. Facts are one-shot tests that assert (hopefully) one thing. Theories are tests that either can not be completely verified with a single assertion, or need to have multiple inputs verified separately.

xUnit++ supports running tests concurrently, and will also randomize the test execution with each run. Once a Theory is declared, all instances of that Theory are treated as individual tests.

Facts

FACT("Widget default Constructor and Destructor will never throw")
{
    Assert.DoesNotThrow([]() { Widget w; });
}

A Fact is a statement about your code under test: "I assert the default Widget constructor, and the destructor, will never throw exceptions."

FACT is a macro that takes care of registering your test with the internal test collection. The only parameter is the test name.

There are several variants:

  • TIMED_FACT indicates that you expect a test will complete within a certain amount of time, or the test should be aborted and marked as a failure. The test timeout is the last parameter and should be given in milliseconds.
  • UNTIMED_FACT indicates that this is a known long-running test that is not subject to the test runner's default test timeout.
  • [[UN]TIMED_]FACT_FIXTURE takes a struct or class fixture type as their second argument. Your test will publicly derive from that class, and a new instance will be created for each test. Use the fixture's constructor and destructor as setup and teardown functions. The optional TIMED_ and UNTIMED_ prefixes operate as you would expect.
struct MyFixture
{
    MyFixture() { /* setup */ }
    ~MyFixture() { /* teardown */ }

    int someData;
};

FACT_FIXTURE("My Test", MyFixture)
{
    int temp = someData;
}
TIMED_FACT("Quick Test", 5)
{
    // test code that must complete within 5 milliseconds
}

Theories

THEORY("Widget Destructor should never throw", (Widget *w),
    std::make_tuple(new Widget((int)0)),
    std::make_tuple(new Widget(new Foo())),
    std::make_tuple(new Widget(nullptr))
)
{
    Assert.DoesNotThrow([&]() { delete w; });
}

A Theory is for more abstract tests. It is intended for tests that can't verify methods with single input, single output pairs, or when the test code would otherwise need to be repeated in multiple FACTS.

In this example, we want to assert that a fully-constructed, valid Widget will not throw exceptions when destroyed. Widget, however, has multiple methods for constructions, and the destructors for objects created through each of these need to be tested. To do this, the THEORY macro takes two named parameters: the test name and the parameters to be passed to the test (combined they form the function signature), and then a collection of several std::tuples that make up the test data.

There are several forms of THEORY as well.

  • DATA_THEORY accepts a data provider function object that returns a container of std::tuple that supports begin/end instead a list of inline data. This lets you define more complex methods of providing the test data. For example, your test data could come from an external database. You can also pass in lambdas as the test parameter, and with that we could re-write the two above tests thusly:
std::vector<std::tuple<std::function<void()>>> DataProvider()
{
    std::vector<std::tuple<std::function<void()>>> data;

    data.emplace_back([]() { Widget w; });
    data.emplace_back([]() { Widget w((int)0); });
    data.emplace_back([]() { Widget w(new Foo()); });
    data.emplace_back([]() { Widget w(nullptr); });

    return data;
}

DATA_THEORY("The Widget Constructors and the Destructor will never throw", (std::function<void()> makeAndDestroyWidget), DataProvider)
{
    Assert.DoesNotThrow(makeAndDestroyWidget);
}
  • Theories also have the [UN]TIMED_ variants as well, and they operate just like the FACT variants.

Test Timeouts

A note on the test timeouts: this is inherently very fragile. Once a thread begins executing, there is no guarantee that the test will actually get n milliseconds of CPU time. It's possible that other tests or system processes will take control of the CPU for the majority of the time, leaving the watchdog timeout thread no choice but to assume the test thread failed to complete within the allotted time.

Updated