Building Cgreen test suites

This page...

Cgreen is a tool for building unit tests in the C language. 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.

Writing basic tests

Cgreen tests are simply C functions with no parameters and a void return value. An example might be...

static void strlen_of_hello_should_be_five() {
    assert_equal(strlen("Hello"), 5);
}
The test function name can be anything you want. The assert_equal() call is an example of an assertion. Assertions send messages to Cgreen, which in turn outputs the results.

Here are the standard assertions...
AssertionDescription
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
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 assert_equal() is used on char * pointers then the pointers have to point at the same string.

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(first, second, message, ...)
assert_not_equal_with_message(first, second, 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...

static void strlen_of_hello_should_be_five() {
    const char *greeting = "Hello";
    int length = strlen(greeting);
    assert_equal_with_message(length, 5,
            "[%s] should be 5, but was %d", greeting, length);
}
A slightly more user friendly message when things go wrong.

For the test above to work there needs to be a running test suite. We can create one expecially for this test like so...

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, strlen_of_hello_should_be_five);
    return suite;
}
In case you have spotted that strlen_of_hello_should_be_five() should have an ampersand in front of it, add_test() is a macro. The & is added automatically.

To run the test suite, we call run_test_suite() on it. This function cleans up the test suite after running it, so we can just write...

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. The only predefined one in Cgreen is the TextReporter that delivers messages in plain text.

A full test script now looks like...

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

static void strlen_of_hello_should_be_five() {
    assert_equal(strlen("Hello"), 5);
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, strlen_of_hello_should_be_five);
    return suite;
}

int main(int argc, char **argv) {
    return run_test_suite(our_tests(), create_text_reporter());
}
The return value of run_test_suite() is a Unix exit code.

Compliling and running gives...

gcc -c strlen_test.c
gcc strlen_test.o cgreen/cgreen.a -o strlen_test
./strlen_test
Running "main"...
Completed "main": 1 pass, 0 failures, 0 exceptions.
The test messages are only shown on failure. If we break our test...
static void strlen_of_hello_should_be_five() {
    assert_equal(strlen("Hiya", 5);
}
...we'll get the helpful message...
Running "main"...
Failure!: strlen_of_hello_should_be_five ->
        [5] shold be [4] at [strlen_test.c] line [6]
Completed "main": 0 passes, 1 failure, 0 exceptions.
Cgreen appends the location of the test failure to our error string.

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...

...
static void strlen_of_empty_string_should_be_zero() {
    assert_equal(strlen("\0"), 0);
}

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, strlen_of_hello_should_be_five);
    add_test(suite, strlen_of_empty_string_should_be_zero);
    return suite;
}
...
And so on.

Set up and tear down

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/mysql.h>
#include "person.h"

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);
}

static void 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_string_equal(get_person_name(person), "Fred");
    drop_schema();
}

static void cannot_add_duplicate_person() {
    create_schema();
    Person *person = create_person();
    set_person_name(person, "Fred");
    assert_true(save_person(person));
    Person *duplicate = create_person();
    set_person_name(duplicate, "Fred");
    assert_false(save_person(duplicate));
    drop_schema();
}

TestSuite *person_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, can_add_person_to_database);
    add_test(suite, 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 it's own functions create_scheme() and drop_schema(), so things are not so bad. At least not yet. What happens when we get dozens of tests? For a test subject as compicated as a database ActiveRecord, having dozens of tests is very likely.

We can get Cgreen to do some of the work for us by declaring these methods as setup() and teardown() functions in the test suite.

Here is the new version...

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

static void drop_schema() { ... }

static void 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_string_equal(get_person_name(person), "Fred");
}

static void cannot_add_duplicate_person() {
    Person *person = create_person();
    set_person_name(person, "Fred");
    assert_true(save_person(person));
    Person *duplicate = create_person();
    set_person_name(duplicate, "Fred");
    assert_false(save_person(duplicate));
}

TestSuite *person_tests() {
    TestSuite *suite = create_test_suite();
    setup(suite, create_schema);
    teardown(suite, drop_schema);
    add_test(suite, can_add_person_to_database);
    add_test(suite, cannot_add_duplicate_person);
    return suite;
}
...
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 that 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.

A couple of details. Currently only one setup() and teardown() may be added to each TestSuite. Also the teardown() function may not be run if the test crashes, causing some test interference. This brings us nicely onto the next section...

Each test in it's own process

Consider this test method...


void will_seg_fault() {
    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 it's own process.

Just before the setup() call, Cgreen fork()'s. The main process wait's for the test to complete normally or die. This includes the teardown(). If the test process dies, an exception is reported and the main test process carries on.

For example...

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

static void will_seg_fault() {
    int *p = NULL;
    (*p)++;
}

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_test(suite, will_seg_fault);
    run_test_suite(suite, create_text_reporter());
}
When built and run, this gives...
Running "main"...
Exception!: will_seg_fault -> Test "will_seg_fault" failed to complete
Completed "main": 0 passes, 0 failures, 1 exception.
The obvious thing to do now is to fire up the debugger. Unfortunately, the constant fork()'ing of Cgreen can be an 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...

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(suite, will_seg_fault);
    run_single_test(suite, "will_seg_fault", create_text_reporter());
}
When run in this way, Cgreen will not fork().

This deals with the segmentation fault case, but what about a process that fails to complete by getting stuck in a loop?

Well, Cgreen will wait forever too. 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...

...
static void will_seg_fault() { ... }

static void this_would_stall() {
    die_in(1);
    while(0 == 0) { }
}

int main(int argc, char **argv) {
    TestSuite *suite = create_test_suite();
    add_test(suite, will_seg_fault);
    add_test(suite, this_would_stall);
    run_test_suite(suite, create_text_reporter());
}
When executed, the code will slow for a second, and then finish with...
Running "main"...
Exception!: will_seg_fault -> Test "will_seg_fault" failed to complete
Exception!: will_stall -> Test "this_would_stall" failed to complete
Completed "main": 0 passes, 0 failures, 2 exceptions.
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 setup() function. Cgreen will then apply the limit to all of them.

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() calls. E.g...

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

static void strlen_of_hello_should_be_five() { ... }
static void strlen_of_empty_string_should_be_zero() { ... }

TestSuite *our_tests() {
    TestSuite *suite = create_test_suite();
    add_test(suite, strlen_of_hello_should_be_five);
    add_test(suite, strlen_of_empty_string_should_be_zero);
    return suite;
}
Then we can write a small runner script with a new main() function...
#include "strlen_tests.c"
#include "person_tests.c"

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 in the runner scripts, 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.

It's sometimes handy to be able to run just a single test 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 recomplile when we debug.

We've placed each test suite in it's own file, but that is not necessary. We could 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() in a TestSuite that contains other TestSuites?

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");
}

static void can_add_person_to_database() { ... }
static void 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();
    setup(suite, create_schema);
    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);
    setup(fixture, open_connection);
    teardown(fixture, close_connection);
    return fixture;
}
The trick here is creating a test suite as a wrapper whose sole purpose 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 is runs 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 it's 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);
    setup(fixture, open_connection);
    teardown(fixture, close_connection);
    return fixture;
}
This allows the reuse of common fixtures across projects.

References and related information...