Matteo Caprari home

[C Testing] MinUnit - less is more

posted on 08 Sep 2008
- Can a whole framework be 3 lines long? -

The C project I'm working on is quite small in LOC and files. It won't grow much as it's deployed on a chip with 4k program space and 256 bytes of ram.

No matter how small, test all your code. Always. Period.

Problem is, where to start?

Approaching unit testing may go down two different paths.

Write few functions with ifs and printfs, call them from a main(), execute and see. It's prone to code duplication and you'll soon start reinventing the wheel. You are paving your way to a maintainability hell. Anyway, It might work for small projects and is very easy to get started with. 

Otherwise you can embrace a framework such as CppUnit o Check, learn it and start writing tests. You get more power and less duplication. You'll benefit from standardization, automation and much more. It's the best approach, except for the steep learning curve. Add that writing tests is not exciting nor easy, and you might end up being so frustrated that you'll never get your test suite done. 

Introducing the solution: MinUnit, a unit testing framework in 3 lines of code (!!!!):

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

(This kind of magic is why I really do miss macros in Java)

At least it is the solution for my small-scale project.

Just copy those few lines in a header file, include it and start writing tests. It is really that easy. And gone is your excuse for not testing. 

Really, this might be all you need even for a mid-sized project. Especially if you are introducing unit testing in an existing project, the jump start can really pay off.

One important feature of (some) bigger frameworks is that the test runner executes your test code in a separate address space. This way if your buggy code picks the wrong pointer, it won't bring down the test runner, but only that unit. The runner will report it as a failure and move on. Do that with MinUnit and probably you'll have no clue of what hit you.

Make no excuses, test-your-code.

For the records, I modified it a little bit, adding a specialized assert:
#define mu_assert_eq(expected, actual) {\
int a = (expected); \
int b = (actual); \
do { if ((a) != (b)) { \
sprintf(minunit_msg, "FAILURE %s:%d expected %d, got %u", __FILE__, __LINE__, a, b);\
return minunit_msg; }; \
} while(0); }

There is a complete example on the project page or have a look at a real world example: test_ring_buffer.h and test.c

Matteo

blog comments powered by Disqus