Writing Tests

Contexts

Tests can live inside contexts, which can be nested to an arbitrary depth. This example illustrates using contexts:

static constexpr const int a = 1;

static Suite u("the root context",
    given("a test can go here", [] (auto& check) {
        check("`a` is one", VAR(a) == 1);
    }),
    context("a context",
        given("or a test can go here", [] (auto& check) {
            check("`a` is one", VAR(a) == 1);
        }),
        context("contexts can be nested to arbitrary depth",
            given("or a test can go here", [] (auto& check) {
                check("`a` is one", VAR(a) == 1);
            })
        )
    )
);

and, with verbosity checks, will output:

the root context
    Given: a test can go here
    Then : `a` is one

the root context/a context
    Given: or a test can go here
    Then : `a` is one

the root context/a context/contexts can be nested to arbitrary depth
    Given: or a test can go here
    Then : `a` is one

When Blocks

A test can have a number of when blocks inside it. The test is run once for each when block inside it, or just once if there are no when blocks.

given("a variable `a = 0`", [] (auto& check) {
    int a = 0;

    check.when("we add 1 to it", [&] {
        a += 1;
        check(VAR(a) == 1);
    });

    check.when("we add 2 to it", [&] {
        a += 2;
        check(VAR(a) == 2);
    });

    check.when("we add 3 to it", [&] {
        a += 3;
        check(VAR(a) == 3);
    });
}),

This will output:

Given: a variable `a = 0`
When : we add 1 to it
Then : (a == 1)

Given: a variable `a = 0`
When : we add 2 to it
Then : (a == 2)

Given: a variable `a = 0`
When : we add 3 to it
Then : (a == 3)

A when block can also have nested when blocks. The test is run freshly for each nested when block as well. For example this test will run 4 times:

given("a variable `a = 0`", [] (auto& check) {
    int a = 0;

    check.when("we add 1 to it", [&] {
        a += 1;
        check(VAR(a) == 1);

        check.when("we add 10 to it", [&] {
            a += 10;
            check(VAR(a) == 11);
        });

        check.when("we add 20 to it", [&] {
            a += 20;
            check(VAR(a) == 21);
        });
    });

    check.when("we add 2 to it", [&] {
        a += 2;
        check(VAR(a) == 2);
    });

    check.when("we add 3 to it", [&] {
        a += 3;
        check(VAR(a) == 3);
    });
}),

You can see the individual runs of the test and the flow of control from the output:

Given: a variable `a = 0`
When : we add 1 to it
Then : (a == 1)
    When : we add 10 to it
    Then : (a == 11)

Given: a variable `a = 0`
When : we add 1 to it
Then : (a == 1)
    When : we add 20 to it
    Then : (a == 21)

Given: a variable `a = 0`
When : we add 2 to it
Then : (a == 2)

Given: a variable `a = 0`
When : we add 3 to it
Then : (a == 3)

Parameterized Tests

Sometimes you want to write several very similar tests. Let’s test a function multiply, that just multiplies its arguments together:

int multiply(int x, int y) { return x * y; }

We can test several parameters with the following test:

void testMultiply(Check& check, int x, int y, int expectedResult) {
    check.when("we multiply them together with `multiply`", [&] {
        check(VAR(multiply) (x, y) == expectedResult);
    });
}

And to register all the tests for every combination of 0, 1 and 10 on each arguments:

    given("0 and 0",   testMultiply, 0, 0, 0),
    given("0 and 1",   testMultiply, 0, 1, 0),
    given("0 and 10",  testMultiply, 0, 10, 0),
    given("1 and 0",   testMultiply, 1, 0, 0),
    given("1 and 1",   testMultiply, 1, 1, 1),
    given("1 and 10",  testMultiply, 1, 10, 10),
    given("10 and 0",  testMultiply, 10, 0, 0),
    given("10 and 1",  testMultiply, 10, 1, 10),
    given("10 and 10", testMultiply, 10, 10, 100),

which will output:

Given: 0 and 0
When : we multiply them together with `multiply`
Then : (multiply(0, 0) == 0)

Given: 0 and 1
When : we multiply them together with `multiply`
Then : (multiply(0, 1) == 0)

Given: 0 and 10
When : we multiply them together with `multiply`
Then : (multiply(0, 10) == 0)

Given: 1 and 0
When : we multiply them together with `multiply`
Then : (multiply(1, 0) == 0)

Given: 1 and 1
When : we multiply them together with `multiply`
Then : (multiply(1, 1) == 1)

Given: 1 and 10
When : we multiply them together with `multiply`
Then : (multiply(1, 10) == 10)

Given: 10 and 0
When : we multiply them together with `multiply`
Then : (multiply(10, 0) == 0)

Given: 10 and 1
When : we multiply them together with `multiply`
Then : (multiply(10, 1) == 10)

Given: 10 and 10
When : we multiply them together with `multiply`
Then : (multiply(10, 10) == 100)

Model Checking

Sometimes we want to check against a model. Lets test multiply with a model that says x * y is the same as adding y to 0, x times. This will run the test for every possible combination of choice(0, 1, 10) on x and y:

    exhaustive(choice(0, 1, 10), choice(0, 1, 10)).
        given("2 numbers", [] (Check& check, int x, int y) {
            check.when("we multiply them together", [&] {
                int result = multiply(x, y);

                // This is our model to check multiply against.
                int expected = 0;

                for (int i = 0; i < x; ++i) {
                    expected += y;
                }

                check(VAR(result) == VAR(expected), VAR(x), VAR(y));
            });
        }
    ),

The function choice is a wrapper around vector. It infers the type from it’s first argument and creates the vector. We could use any stl container with forward iterators, like boost::irange for example.