Skip to content

Unit testing with check

A friend recently introduced me to the virtues of unit testing, which is an approach to coding where, for every function, data structure, class, or whatever piece of code, you write test cases that show it to work. The main virtues of such an approach, as far as I understand, are that 1) you are probably going to write tests and spend some time debugging, often amounting to even more time than if you systematically tested every piece of code 2) you are forced to think how the code will be used, and in what cases it may fail 3) you are less wary of rewriting some code, since you can easily determine whether the system is likely to remain in good shape after your changes.

It might seem cumbersome initially, but the fact is that frameworks exist that simplify the task of writing tests and running them. Moreover, as explained, you are very likely to waste even more time on debugging and writing dirty tests, especially if the project gets big. After some thinking, I can clearly see how it will pay off in the not-so-long run. A book that sells this idea pretty well is Clean Code: a Handbook of Agile Software Craftsmanship, which I would recommend every CS student to read, and every manager hand to their developers.

So, I decided to give the idea a try in a private coding project. The code is in C, so I googled for a suitable unit testing framework. I quickly found Check, had a quick look and found it to be easy to learn enough, and to be available in my the package managers of the linux distributions I use. So I did not look any further. So, a quick example of how to define a few tests using check:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int factorial(int x){
    if (x == 0)
        return 1;
    return x * factorial(x - 1);
}
 
START_TEST(test_factorial_basic)
{
    fail_unless(factorial(5) == 120);
}
END_TEST
 
START_TEST(test_factorial_zero)
{
    fail_unless(factorial(0) == 1);
}
END_TEST

Then, as explained in the manual of check, START_TEST and END_TEST are macros that end up defining functions called test_factorial_whatever. You then have to add some code to call such functions. For example, you can have an alternative main function that is compiled in a separate binary that runs the tests. The code you have to write involves creating test suites, adding test functions to them, and finally running the whole bunch of tests. So, when writing a new test, I would need both to define it and then add it to the list of tests to be ran. I immediately knew that I needed some mechanism to ensure every test I’d be defining would be ran. This way, I can’t forget to run the tests I write, and writing tests becomes less cumbersome. Ideally, at every test definition, so I began working in the direction of making it work like this:

1
2
3
4
5
START_AND_REGISTER_TEST(test_factorial_basic)
{
    fail_unless(factorial(5) == 120);
}
END_TEST

So I began wondering, how do I define a macro that can add test cases to a list of tests to be ran? I figured a solution could be to write a script that parses the C files, discovers tests and generates a C file that contains an array of functions to be called. This would certainly work, but it’s quite a hack, isn’t it. How could we do this while staying in C territory? I do not have the definite answer to this. I did find a solution that works in gcc territory, which is something. The solution I found is quite a hack anyways, but I found it cool enough to share it.

I discovered that gcc lets you define functions that run right before main is. gcc calls those constructor functions. For example,

1
2
3
4
__attribute__((constructor))
void helloworld(void) {
    printf("hello, world!\n");
}

That looks like an opportunity to inject some code that runs before main and registers every test. So, I took that route:

1
2
3
4
5
6
7
8
9
#ifdef COMPILE_TESTS
#define REGISTER_TEST(testname)             \
    void __attribute__((constructor))       \
    __registerer_for_test_##testname() {    \
        register_test(testname, #testname); \
    }
 
#define START_AND_REGISTER_TEST(x) \
    static void x(int); REGISTER_TEST(x) START_TEST(x)

This macro still generates the test using check, but, before it is defined, a constructor function is generated that registers it (by calling register_test(test_fn, "test_fn")). You can imagine how the implementation of register_test looks like.

I will soon upload a full example project using this macro to a github repository. Check this github repository for a toy example project.

One Comment

  1. brainstorm wrote:

    You might want to have a look at Julio’s pet project @google too ;)

    http://code.google.com/p/kyua/

    Saturday, September 24, 2011 at 12:19 pm | Permalink

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*