Unit Testing in C

Like any good software developer, I’m sure you’re beginning to wonder how to write sustainable C code. The way to do that in any language is with unit tests. The idea behind unit tests is to have each “unit” of your code be validated with assertive tests. A “unit” is typically a single function, and the goal of unit tests on that function is to validate that the function behaves as you expect for a range of inputs.

While there are many unit testing frameworks for C, for the purposes of this article I’ll be using minunit. Minunit is an incredibly simple framework that makes learning how to unit test easy. In fact, the source code is a whole 3 lines (seen below).

To install minunit, copy the below code into a “minunit.h” file. It doesn’t matter where it’s located in your file system, as long as you know how to include it in your unit test files.

#define mu_assert(message, test) do { if (!(test)) return message; } while (0)
#define mu_run_test(test) do { char *message = test(); tests_run++; \
                               if (message) return message; } while (0)
extern int tests_run;

Minunit provides two critical macros, mu_assert and mu_run_test. mu_assert takes a message and a statement that evaluates to a boolean value (i.e. my_int == 5), returning the message if the boolean value is false. mu_run_test handles calling the functions containing all of your mu_assert calls.

A simple test suite using minunit looks something like this:

#include <stdio.h>
#include "minunit.h"

int tests_run = 0;

// Here's a sample function we'll test
int add(int x, int y) {
    return x + y;
}

// Here's another sample function,
// except this one has an error (* instead of +)
int bad_add(int x, int y) {
    return x * y;
}

// Testing the add() function
static char * test_add() {
    mu_assert("error, adding 2 and 3 != 5", add(2, 3) == 5);
    return 0;
}

// Testing the bad_add() function
static char * test_bad_add() {
    mu_assert("error, adding 2 and 3 != 5", bad_add(2, 3) == 5);
    return 0;
}

// Here is where we call all of the unit test functions
static char * all_tests() {
    mu_run_test(test_add);
    mu_run_test(test_bad_add);
    return 0;
}

int main(int argc, char **argv) {
    char *result = all_tests();
    // result will be the failed test message if a test fails
    if (result != 0) {
        printf("%s\n", result);
    }
    else {
        printf("ALL TESTS PASSED\n");
    }
    printf("Tests run: %d\n", tests_run);

    return result != 0;
}

test_add and test_bad_add are the unit testing functions in this example. They should contain 1 or more calls of mu_assert that should test something regarding the functionality of add and bad_add, respectively. A good unit test should have a representative sample of the range of possible inputs as well as a representative sample of the expected domain of the function being tested.

As a note, the above code does not do this well. For example, if I had chosen to have the assertions testing adding 2 and 2, both unit tests would have passed because the bad_add function would find 2 * 2 = 4, which is the expected result as well.

In minunit, there is no automation regarding running the test suite. As such, you have to call mu_run_test on each unit test function, which is aggregated in the all_tests function in the above sample.

While the functionality of minunit is limited, I think it makes a good place to start with unit testing. When you feel comfortable with the idea of unit testing, I’d recommend going and finding another, more feature-rich alternative. You can find an excellent discussion regarding the available choices on Stack Overflow.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s