I have recently read Test Driven Development with Embedded C by James W. Grenning and published by Pragmatic Programmers.
I really enjoyed the book: while I was aware of the huge benefits of having a comprehensive test suite, I never studied seriously the principles behind Test Driven Development (TDD) and this book makes a good introduction to the topic. At the same time it focuses on the C language and contains lots of examples on how you can create tests even for projects which have to interact with hardware or other unpredictable components (the key is to create many abstractions) using all the possibilities that C offers.
The author convincingly argues that developing code with TDD forces you to create a modular design that is easier to evolve when the underlying requirements change. He also highlights how the tests serve as reference documentation of the API.
James W. Grenning recommends CppUTest as his xUnit test framework of choice. When I wanted to try this test framework, I discovered that it was not available in Debian. I decided to package it because it has some interesting features not offered by the contenders (at least not to my knowledge). It’s now available in Debian and in Ubuntu.
First, it doesn’t require any explicit registration of tests and has a very lightweight syntax. The small downside is that CppUTest requires the usage of C++ for the tests. But C++ is compatible with C so it doesn’t matter much if you have a C++ compiler for your target. On the contrary, usage of variables and methods scoped to the test group makes it easy to write clear tests. Here’s a short sample of test code:
extern "C" { #include "timer.h" #include "timefn.h" } #include "CppUTest/TestHarness.h" static Time the_time; static const int start_sec = 123; static const int start_nsec = 456789000; static const int delay_sec = 8; static const int delay_nsec = 111111000; // start_nsec + delay_nsec < 10^9 TEST_GROUP(Timer) { /* Class variables available to all tests in the group */ Timer timer; Delay remaining; /* Standard setup/teardown methods of xUnit tests */ void setup() { timer = timer_new(); time_set(&the_time, start_sec, start_nsec); /* [...] */ } void teardown() { timer_free(timer); /* [...] */ } /* Helper functions specific to the test group */ void start_timer_with_delay(long sec, long nsec) { timer_set_real_delay(timer, sec, nsec); timer_start(timer); } void ensure_remaining_is(long sec, long nsec) { CHECK_EQUAL(sec, delay_get_seconds(remaining)); CHECK_EQUAL(nsec, delay_get_nanoseconds(remaining)); } }; TEST(Timer, NewIsNotStarted) { CHECK(!timer->started); } /* [...] */ TEST(Timer, GetRemainingTimeWithNanosecondPrecision_ShiftOfSeconds) { start_timer_with_delay(delay_sec, delay_nsec); time_set(&the_time, start_sec + delay_sec - 5, start_nsec + delay_nsec + 1000); remaining = timer_get_remaining_time(timer); ensure_remaining_is(4, 999999000); } |
To run those tests, you just need this boilerplate code in a main.cpp:
#include "CppUTest/CommandLineTestRunner.h" int main(int argc, char** argv) { return CommandLineTestRunner::RunAllTests(argc, argv); } |
Another interesting feature is its integrated memory leak detection system. Any test that hasn’t released allocated memory at the end of the “teardown” process will be marked as failed.
The upstream developers have made some unusual choices (static library only, installation in a private directory) but this will likely change with the switch to an automake and autoconf-based build system. I have reported the oddities that I found and I requested them to provide a pkg-config file to make it easier to compile and link unit tests exploiting CppUTest.
I already used CppUTest to develop a small application running on an embedded Linux. At some point, I might try to use CppUTest for dpkg development. I believe that it makes for a good fit. dpkg is already C++ ready since dselect is written in C++ and reuses a good part of dpkg’s code.
In any case, if you like Test Driven Development and are writing C or C++ based applications, I invite you to try CppUTest.