2. Building Cgreen test suites

Cgreen is a tool for building unit tests in the C or C++ languages. These are usually written alongside the production code by the programmer to prevent bugs. Even though the test suites are created by software developers, they are intended to be human readable C code, as part of their function is an executable specification. Used in this way, the test harness delivers constant quality assurance.

In other words you’ll get less bugs.

2.1. Writing Basic Tests

Cgreen tests are like C, or C++, functions with no parameters and no return value. To signal that they actually are tests we mark them with the Ensure macro. Here’s an example…​

Ensure(Strlen, returns_five_for_hello) {
    assert_that(strlen("Hello"), is_equal_to(5));
}

The Ensure macro takes two arguments (in the BDD style) where the first is the System Under Test (SUT) which must be declared with the Describe macro.

Describe(Strlen);

The second argument is the test name and can be anything you want as long as it fullfills the rules for an identifier in C and C++. A typical way to choose the named of the tests is what we see here, reading the declaration of the test makes sense since it is almost plain english, "Ensure strlen returns five for 'hello'". No problem understanding what we aim to test, or in TDD lingo, test drive. And it can be viewed as an example from a description of what strlen should be able to do. In a way, extracting all the Ensure:s from your test might give you all the documentation you’ll need.

The call to assert_that() is the primary part of an assertion, which is complemented with a constraint, in this case is_equal_to(), as a parameter. This makes a very fluent interface to the asserts, that actually reads like English. The general format is then

assert_that(actual, <constraint>);
Sometimes you just want to fail the test explicitly, and there is a function for that too, fail_test(const char *message). And there is a function to explicitly pass, pass_test(void).

Assertions send messages to Cgreen, which in turn outputs the results.

2.2. The Standard Constraints

Here are the standard constraints…​

Constraint

Passes if actual value/expression…​

Basic

is_true

evaluates to true, buy you can also just leave out the constraint, e.g. assert_that(found) if found is of boolean type

is_false

evaluates to false

is_null

equals null

is_non_null

is a non null value

is_not_null

d:o

Integer compatible

is_equal_to(value)

'== value'

is_equal_to_hex(value)

'== value', but will show values in HEX

is_not_equal_to(value)

'!= value'

is_greater_than(value)

'> value'

is_less_than(value)

'< value'

Structs and general data

is_equal_to_contents_of(pointer, size)

matches the data pointed to by pointer to a size of size bytes

is_not_equal_to_contents_of(pointer, size)

does not match the data pointed to by pointer to a size of size bytes

Strings

is_equal_to_string(value)

are equal when compared using strcmp()

is_not_equal_to_string(value)

are not equal when compared using strcmp()

contains_string(value)

contains value when evaluated using strstr()

does_not_contain_string(value)

does not contain value when evaluated using strstr()

begins_with_string(value)

starts with the string value

does_not_begin_with_string(value)

does not start with the string value

ends_with_string(value)

ends with the string value

does_not_end_with_string(value)

does not end with the string value

Double floats

is_equal_to_double(value)

are equal to value within the number of significant digits (which you can set with a call to significant_figures_for_assert_double_are(int figures))

is_not_equal_to_double(value)

are not equal to value within the number of significant digits

is_less_than_double(value)

< value withing the number of significant digits

is_greater_than_double(value)

> value within the number of significant digits

The boolean assertion macros accept an int value. The equality assertions accept anything that can be cast to intptr_t and simply perform an == operation. The string comparisons are slightly different in that they use the <string.h> library function strcmp(). If you use is_equal_to() with char * pointers then it is the value of the pointers themselves that has to be the same, i.e. the pointers have to point at the same string for the test to pass.

The constraints above should be used as the second argument to one of the assertion functions:

Assertion

Description

assert_that(expected, constraint)

Passes if expected fullfulls constraint, to be used for all assertions except double type values

assert_that_double(expected, constraint)

Passes if expected fullfulls constraint, only to be used for assertions on double type values

You cannot use C/C++ string literal concatenation (like "don’t" "use" "string" "concatenation") in the parameters to the constraints. If you do, you will get weird error messages about missing arguments to the constraint macros. This is caused by the macros using argument strings to produce nice failure messages.

2.3. Asserting C++ Exceptions

When you use CGreen with C++ there is one extra assertion available:

Assertion

Description

assert_throws(exception, expression)

Passes if evaluating expression throws exception

2.4. BDD Style vs. TDD Style

So far we have encouraged the modern BDD style. It has merits that we really want you to benefit from. But you might come across Cgreen test in another style, more like the standard TDD style, which is more inline with previous thinking and might be more similar to other frameworks.

The only difference, in principle, is the use of the SUT or 'context'. In the BDD style you have it, in the TDD style you don’t.

BDD style:
Describe(Strlen);                                                 (1)
BeforeEach(Strlen) {}                                             (2)
AfterEach(Strlen) {}                                              (3)

Ensure(Strlen, returns_five_for_hello) {                          (4)
    assert_that(strlen("Hello"), is_equal_to(5));
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Strlen, returns_five_for_hello); (5)
    return suite;
}
1 The Describe macro must name the SUT
2 The BeforeEach function…​
3 …​ and the AfterEach functions must exist and name the SUT
4 The test need to name the SUT
5 Adding to the test suite
You can only have tests for a single SUT in the same source file.

If you use the older pure-TDD style you skip the Describe macro, the BeforeEach and AfterEach functions. You don’t need a SUT in the Ensure() macro or when you add the test to the suite.

TDD style:
                                                               (1)
Ensure(strlen_returns_five_for_hello) {                        (2)
    assert_that(strlen("Hello"), is_equal_to(5));
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, strlen_returns_five_for_hello);            (3)
    return suite;
}
1 No Describe, BeforeEach() or AfterEach()
2 No SUT/context in the Ensure() macro
3 No SUT/context in add_test() and you should use this function instead of ..with_context().
You might think of the TDD style as the BDD style with a default SUT or context.

2.5. Legacy Style Assertions

Cgreen have been around for a while, developed and matured. There is an older style of assertions that was the initial version, a style that we now call the 'legacy style', because it was more aligned with the original, now older, unit test frameworks. If you are not interested in historical artifacts, I recommend that you skip this section.

But for completeness of documentation, here are the legacy style assertion macros:

Assertion

Description

assert_true(boolean)

Passes if boolean evaluates true

assert_false(boolean)

Fails if boolean evaluates true

assert_equal(first, second)

Passes if 'first == second'

assert_not_equal(first, second)

Passes if 'first != second'

assert_string_equal(char *, char *)

Uses 'strcmp()' and passes if the strings are equal

assert_string_not_equal(char *, char *)

Uses 'strcmp()' and fails if the strings are equal

Each assertion has a default message comparing the two values. If you want to substitute your own failure messages, then you must use the *_with_message() counterparts…​

Assertion

assert_true_with_message(boolean, message, …​)

assert_false_with_message(boolean, message, …​)

assert_equal_with_message(tried, expected, message, …​)

assert_not_equal_with_message(tried, unexpected, message, …​)

assert_string_equal_with_message(char *, char *, message, …​)

assert_string_not_equal_with_message(char *, char *, message, …​)

All these assertions have an additional char * message parameter, which is the message you wished to display on failure. If this is set to NULL, then the default message is shown instead. The most useful assertion from this group is assert_true_with_message() as you can use that to create your own assertion functions with your own messages.

Actually the assertion macros have variable argument lists. The failure message acts like the template in printf(). We could change the test above to be…​

Ensure(strlen_of_hello_is_five) {
    const char *greeting = "Hello";
    int length = strlen(greeting);
    assert_equal_with_message(length, 5, "[%s] should be 5, but was %d", greeting, length);
}

This should produce a slightly more user friendly message when things go wrong. But, actually, Cgreens default messages are so good that you are encouraged to skip the legacy style and go for the more modern constraints style assertions. Particularly in conjuction with the BDD style test notation.

We strongly recommend the use of BDD Style notation with constraints based assertions.

2.6. A Runner

The tests are only run by running a test suite in some form. (But see also [runner].) We can create and run one especially for this test like so…​

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Strlen, returns_five_for_hello);
    return suite;
}

In case you have spotted that the reference to returns_five_for_hello should have an ampersand in front of it, add_test_with_context() is actually a macro. The & is added automatically. Further more, the Ensure()-macro actually mangles the tests name, so it is not actually a function name. (This might also make them a bit difficult to find in the debugger…​.)

To run the test suite, we call run_test_suite() on it. So we can just write…​

    return run_test_suite(our_tests(), create_text_reporter());

The results of assertions are ultimately delivered as passes and failures to a collection of callbacks defined in a TestReporter structure. There is a predefined TestReporter in Cgreen called the TextReporter that delivers messages in plain text like we have already seen.

The return value of run_test_suite() is a standard C library/Unix exit code that can be returned directly by the main() function.

The complete test code now looks like…​

#include <cgreen/cgreen.h>
#include <string.h>

Describe(Strlen);
BeforeEach(Strlen) {}
AfterEach(Strlen) {}

Ensure(Strlen, returns_five_for_hello) {
    assert_that(strlen("Hello"), is_equal_to(5));
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Strlen, returns_five_for_hello);
    return suite;
}

int main(int argc, char **argv) {
    return run_test_suite(our_tests(), create_text_reporter());
}

Compiling and running gives…​

$ gcc -c strlen_test.c
$ gcc strlen_test.o -lcgreen -o strlen_test
$ ./strlen_test
Running "our_tests" (1 tests)...
  "our_tests": 1 pass in 42ms.
Completed "our_tests": 1 pass in 42ms.

We can see that the outer test suite is called our_tests since it was in our_tests() we created the test suite. There are no messages shown unless there are failures. So, let’s break our test to see it…​

Ensure(Strlen, returns_five_for_hello) {
    assert_that(strlen("Hiya"), is_equal_to(5));
}

…​we’ll get the helpful message…​

Running "our_tests" (1 tests)...
strlen_tests.c:9: Failure: returns_five_for_hello
	Expected [strlen("Hiya")] to [equal] [5]
		actual value:			[4]
		expected value:			[5]

  "our_tests": 1 failure in 42ms.
Completed "our_tests": 1 failure in 42ms.

Cgreen starts every message with the location of the test failure so that the usual error message identifying tools (like Emacs’s next-error) will work out of the box.

Once we have a basic test scaffold up, it’s pretty easy to add more tests. Adding a test of strlen() with an empty string for example…​

...
Ensure(Strlen, returns_zero_for_empty_string) {
    assert_equal(strlen("\0"), 0);
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Strlen, returns_five_for_hello);
    add_test_with_context(suite, Strlen, returns_zero_for_empty_string);
    return suite;
}
...

And so on.

2.7. BeforeEach and AfterEach

It’s common for test suites to have a lot of duplicate code, especially when setting up similar tests. Take this database code for example…​

#include <cgreen/cgreen.h>
#include <stdlib.h>
#include <mysql.h>
#include "person.h"

Describe(Person);
BeforeEach(Person) {}
AfterEach(Person) {}

static void create_schema() {
    MYSQL *connection = mysql_init(NULL);
    mysql_real_connect(connection, "localhost", "me", "secret", "test", 0, NULL, 0);
    mysql_query(connection, "create table people (name, varchar(255) unique)");
    mysql_close(connection);
}

static void drop_schema() {
    MYSQL *connection = mysql_init(NULL);
    mysql_real_connect(connection, "localhost", "me", "secret", "test", 0, NULL, 0);
    mysql_query(connection, "drop table people");
    mysql_close(connection);
}

Ensure(Person, can_add_person_to_database) {
    create_schema();
    Person *person = create_person();
    set_person_name(person, "Fred");
    save_person(person);
    Person *found = find_person_by_name("Fred");
    assert_that(get_person_name(found), is_equal_to_string("Fred"));
    drop_schema();
}

Ensure(Person, cannot_add_duplicate_person) {
    create_schema();
    Person *person = create_person();
    set_person_name(person, "Fred");
    assert_that(save_person(person), is_true);
    Person *duplicate = create_person();
    set_person_name(duplicate, "Fred");
    assert_that(save_person(duplicate), is_false);
    drop_schema();
}

TestSuite *person_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Person, can_add_person_to_database);
    add_test_with_context(suite, Person, cannot_add_duplicate_person);
    return suite;
}

int main(int argc, char **argv) {
    return run_test_suite(person_tests(), create_text_reporter());
}

We have already factored out the duplicate code into its own functions create_schema() and drop_schema(), so things are not so bad. At least not yet. But what happens when we get dozens of tests? For a test subject as complicated as a database ActiveRecord, having dozens of tests is very likely.

We can get Cgreen to do some of the work for us by calling these methods before and after each test in the test suite.

Here is the new version…​

...
static void create_schema() {
    ...
}

static void drop_schema() {
    ...
}

Describe(Person);
BeforeEach(Person) { create_schema(); }
AfterEach(Person) { drop_schema(); }

Ensure(Person, can_add_person_to_database) {
    Person *person = create_person();
    set_person_name(person, "Fred");
    save_person(person);
    Person *found = find_person_by_name("Fred");
    assert_that(get_person_name(found), is_equal_to_string("Fred"));
}

Ensure(Person, cannot_add_duplicate_person) {
    Person *person = create_person();
    set_person_name(person, "Fred");
    assert_that(save_person(person), is_true);
    Person *duplicate = create_person();
    set_person_name(duplicate, "Fred");
    assert_that(save_person(duplicate), is_false);
}

TestSuite *person_tests() {
...

With this new arrangement Cgreen runs the create_schema() function before each test, and the drop_schema() function after each test. This saves some repetitive typing and reduces the chance of accidents. It also makes the tests more focused.

The reason we try so hard to strip everything out of the test functions is the fact that the test suite acts as documentation. In our person.h example we can easily see that Person has some kind of name property, and that this value must be unique. For the tests to act like a readable specification we have to remove as much mechanical clutter as we can.

In this particular case there are more lines that we could move from the tests to BeforeEach():

    Person *person = create_person();
    set_person_name(person, "Fred");

Of course that would require an extra variable, and it might make the tests less clear. And as we add more tests, it might turn out to not be common to all tests. This is a typical judgement call that you often get to make with BeforeEach() and AfterEach().

If you use the pure-TDD notation, not having the test subject named by the Describe macro, you can’t have the BeforeEach() and AfterEach() either. In this case you can still run a function before and after every test. Just nominate any void(void) function by calling the function set_setup() and/or set_teardown() with the suite and the function that you want to run before/after each test, e.g. in the example above set_setup(suite, create_schema); and set_teardown(suite, drop_schema);.

A couple of details. There is only one BeforeEach() and one AfterEach() allowed in each TestSuite. Also, the AfterEach() function may not be run if the test crashes, causing some test interference. This brings us nicely onto the next section…​

2.8. Each Test in its Own Process

Consider this test method…​

Ensure(CrashExample, seg_faults_for_null_dereference) {
    int *p = NULL;
    (*p)++;
}

Crashes are not something you would normally want to have in a test run. Not least because it will stop you receiving the very test output you need to tackle the problem.

To prevent segmentation faults and other problems bringing down the test suites, Cgreen runs every test in its own process.

Just before calling the BeforeEach() (or setup) function, Cgreen fork():s. The main process waits for the test to complete normally or die. This includes calling the AfterEach()(or teardown) function, if any. If the test process dies, an exception is reported and the main test process carries on with the next test.

For example…​

#include <cgreen/cgreen.h>
#include <stdlib.h>

Describe(CrashExample);
BeforeEach(CrashExample) {}
AfterEach(CrashExample) {}

Ensure(CrashExample, seg_faults_for_null_dereference) {
    int *p = NULL;
    (*p)++;
}

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, CrashExample, seg_faults_for_null_dereference);
    return run_test_suite(suite, create_text_reporter());
}

When built and run, this gives…​

Running "main" (1 tests)...
crash_tests.c:8: Exception: seg_faults_for_null_dereference
	Test terminated with signal: Segmentation fault

  "main": 1 exception in 42ms.
Completed "main": 1 exception in 42ms.

The normal thing to do in this situation is to fire up the debugger. Unfortunately, the constant fork():ing of Cgreen can be one extra complication too many when debugging. It’s enough of a problem to find the bug.

To get around this, and also to allow the running of one test at a time, Cgreen has the run_single_test() function. The signatures of the two run methods are…​

  • int run_test_suite(TestSuite *suite, TestReporter *reporter);

  • int run_single_test(TestSuite *suite, char *test, TestReporter *reporter);

The extra parameter of run_single_test(), the test string, is the name of the test to select. This could be any test, even in nested test suites (see below). Here is how we would use it to debug our crashing test…​

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, CrashExample, seg_faults_for_null_dereference);
    return run_single_test(suite, "seg_faults_for_null_dereference", create_text_reporter());
}

When run in this way, Cgreen will not fork(). But see the section on Debugging Cgreen tests.

The following is a typical session:

$ gdb crash2
...
(gdb) break main
(gdb) run
...
(gdb) break run
(gdb) continue
...
Running "main" (1 tests)...

Breakpoint 2, run_the_test_code (suite=suite@entry=0x2003abb0,
    spec=spec@entry=0x402020 <CgreenSpec__CrashExample__seg_faults_for_null_dereference__>,
    reporter=reporter@entry=0x2003abe0) at /cygdrive/c/Users/Thomas/Utveckling/Cgreen/cgreen/src/runner.c:270
270         run(spec);
(gdb) step
run (spec=0x402020 <CgreenSpec__CrashExample__seg_faults_for_null_dereference__>)
    at /cygdrive/c/Users/Thomas/Utveckling/Cgreen/cgreen/src/runner.c:217
217             spec->run();
(gdb) step
CrashExample__seg_faults_for_null_dereference () at crash_test2.c:9
9           int *p = NULL;
(gdb) step
10          (*p)++;
(gdb) step

Program received signal SIGSEGV, Segmentation fault.
0x004011ea in CrashExample__seg_faults_for_null_dereference () at crash_test2.c:10
10          (*p)++;

Which shows exactly where the problem is.

This deals with the case where your code throws an exception like segmentation fault, but what about a process that fails to complete by getting stuck in a loop?

Well, Cgreen will wait forever too. But, using the C signal handlers, we can place a time limit on the process by sending it an interrupt. To save us writing this ourselves, Cgreen includes the die_in() function to help us out.

Here is an example of time limiting a test…​

...
Ensure(CrashExample, seg_faults_for_null_dereference) {
    ...
}

Ensure(CrashExample, will_loop_forever) {
    die_in(1);
    while(0 == 0) { }
}

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, CrashExample, seg_faults_for_null_dereference);
    add_test_with_context(suite, CrashExample, will_loop_forever);
    return run_test_suite(suite, create_text_reporter());
}

When executed, the code will slow for a second, and then finish with…​

Running "main" (2 tests)...
crash_tests.c:8: Exception: seg_faults_for_null_dereference
	Test terminated with signal: Segmentation fault

crash_tests.c:13: Exception: will_loop_forever
	Test terminated unexpectedly, likely from a non-standard exception or Posix signal

  "main": 2 exceptions in 42ms.
Completed "main": 2 exceptions in 42ms.

Note that you see the test results as they come in. Cgreen streams the results as they happen, making it easier to figure out where the test suite has problems.

Of course, if you want to set a general time limit on all your tests, then you can add a die_in() to a BeforeEach() (or setup()) function. Cgreen will then apply the limit to each of the tests in that context, of course.

Another possibility is the use of an environment variable named CGREEN_TIMEOUT_PER_TEST which, if set to a number will apply that timeout to every test run. This will apply to all tests in the same run.

2.9. Debugging Cgreen tests

Cgreen protects itself from being torn down by an exception in a test by fork()-ing each test into a separate process. A catastrophic error will then only affect the child process for that specific test and Cgreen can catch it, rather than crashing too. It can then report the exception and continue with the next test.

2.9.1. No fork, please

If you want to debug any of your tests the constant fork()-ing might make that difficult or impossible. There are also other circumstances that might require that you don’t use fork().

There are two ways to make Cgreen refrain from fork()-ing.

Cgreen does not fork() when only a single test is run by name with the function run_single_test(). To debug you can then obviously set a breakpoint at that test (but note that its actual name probably have been mangled). Cgreen does some book-keeping before actually getting to the test, so a function easier to find might be the one simply called run().

The second way is to define the environment variable CGREEN_NO_FORK. If Cgreen can get that variable from the environment using getenv() it will run the test(s) in the same process. In this case the non-forking applies to all tests run, so all test will run in the same process, namely *Cgreen*s main process.

This might bring your whole test suite down if a single test causes an exception. So it is not a recommended setting for normal use.

2.9.2. Debugging with cgreen-runner

If you use the convenient auto-discovery feature of Cgreen (see Automatic Test Discovery) by running dynamic loadable libraries through cgreen-runner, it might be tricky to figure out to where to put breaks and to get them to "take".

cgreen-runner obviously loads the library (or libraries) with your tests dynamically so the tests are not available before executing the code that loads them.

The function run() is a good place to place a breakpoint.

2.9.3. cgreen-debug

For some platforms a utility script, cgreen-debug, is installed when you install Cgreen. It makes it very convenient to start a debugging session for a particular test.

Find out the logical name of the test, which is composed of the Context and the Testname, in the form <context>:<testname>. Then just invoke cgreen-debug

$ cgreen-debug <library> <context>:<testname>

The script will try to find a debugger, invoke it on the cgreen-runner and break on that test.

Currently it only supports gdb and will prefer cgdb if that’s available.

2.10. Building Composite Test Suites

The TestSuite is a composite structure. This means test suites can be added to test suites, building a tree structure that will be executed in order.

Let’s combine the strlen() tests with the Person tests above. Firstly we need to remove the main() functions. E.g…​

Ensure(Strlen, returns_five_for_hello) {
   ...
}

Ensure(Strlen, returns_zero_for_empty_string) {
   ...
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test_with_context(suite, Strlen, returns_five_for_hello);
    add_test_with_context(suite, Strlen, returns_zero_for_empty_string);
    return suite;
}

Then we can write a small runner with a new main() function…​

#include <cgreen/cgreen.h>

TestSuite *our_tests();
TestSuite *person_tests();

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_suite(suite, our_tests());
    add_suite(suite, person_tests());
    if (argc > 1) {
        return run_single_test(suite, argv[1], create_text_reporter());
    }
    return run_test_suite(suite, create_text_reporter());
}

It’s usually easier to place the TestSuite prototypes directly in the runner source, rather than have lot’s of header files. This is the same reasoning that let us drop the prototypes for the test functions in the actual test scripts. We can get away with this, because the tests are more about documentation than encapsulation.

As we saw above, we can run a single test using the run_single_test() function, and we’d like to be able to do that from the command line. So we added a simple if block to take the test name as an optional argument. The entire test suite will be searched for the named test. This trick also saves us a recompile when we debug.

When you use the BDD notation you can only have a single test subject (which is actually equivalent of a suite) in a single file because you can only have one Describe() macro in each file. But using this strategy you can create composite suites that takes all your tests and run them in one go.

Rewrite pending. The next couple of sections does not reflect the current best thinking. They are remnants of the TDD notation. Using BDD notation you would create separate contexts, each in its own file, with separate names, for each of the fixture cases.
If you use the TDD (non-BDD) notation you can build several test suites in the same file, even nesting them. We can even add mixtures of test functions and test suites to the same parent test suite. Loops will give trouble, however.
If we do place several suites in the same file, then all the suites will be named the same in the breadcrumb trail in the test message. They will all be named after the function the create call sits in. If you want to get around this, or you just like to name your test suites, you can use create_named_test_suite() instead of create_test_suite(). This takes a single string parameter. In fact create_test_suite() is just a macro that inserts the func constant into create_named_test_suite().

What happens to setup and teardown functions in a TestSuite that contains other TestSuite:s?

Well firstly, Cgreen does not fork() when running a suite. It leaves it up to the child suite to fork() the individual tests. This means that a setup and teardown will run in the main process. They will be run once for each child suite.

We can use this to speed up our Person tests above. Remember we were creating a new connection and closing it again in the fixtures. This means opening and closing a lot of connections. At the slight risk of some test interference, we could reuse the connection accross tests…​

...
static MYSQL *connection;

static void create_schema() {
    mysql_query(connection, "create table people (name, varchar(255) unique)");
}

static void drop_schema() {
    mysql_query(connection, "drop table people");
}

Ensure(can_add_person_to_database) { ... }
Ensure(cannot_add_duplicate_person) { ... }

void open_connection() {
    connection = mysql_init(NULL);
    mysql_real_connect(connection, "localhost", "me", "secret", "test", 0, NULL, 0);
}

void close_connection() {
    mysql_close(connection);
}

TestSuite *person_tests() {
    TestSuite *suite = create_test_suite();
    set_setup(suite, create_schema);
    set_teardown(suite, drop_schema);
    add_test(suite, can_add_person_to_database);
    add_test(suite, cannot_add_duplicate_person);

    TestSuite *fixture = create_named_test_suite("Mysql fixture");
    add_suite(fixture, suite);
    set_setup(fixture, open_connection);
    set_teardown(fixture, close_connection);
    return fixture;
}

The trick here is creating a test suite as a wrapper whose sole purpose is to wrap the main test suite in the fixture. This is our 'fixture' pointer. This code is a little confusing, because we have two sets of fixtures in the same test script.

We have the MySQL connection fixture. This will run open_connection() and close_connection() just once at the beginning and end of the person tests. This is because the suite pointer is the only member of fixture.

We also have the schema fixture, the create_schema() and drop_schema(), which is run before and after every test. Those are still attached to the inner suite.

In the real world we would probably place the connection fixture in its own file…​

static MYSQL *connection;

MYSQL *get_connection() {
    return connection;
}

static void open_connection() {
    connection = mysql_init(NULL);
    mysql_real_connect(connection, "localhost", "me", "secret", "test", 0, NULL, 0);
}

static void close_connection() {
    mysql_close(connection);
}

TestSuite *connection_fixture(TestSuite *suite) {
    TestSuite *fixture = create_named_test_suite("Mysql fixture");
    add_suite(fixture, suite);
    set_setup(fixture, open_connection);
    set_teardown(fixture, close_connection);
    return fixture;
}

This allows the reuse of common fixtures across projects.