Writing unit tests using C

| No Comments | No TrackBacks


One of the most important parts of software develop is the tests of the code. If you don't have a good set of unit test and regression test then you can insert important bugs in your code in maintain phase. There are important frameworks for unit tests using java (JUnits), c++ (CppUnits) and even in SmallTalk (SUnit). Searching in the web I found some frameworks for C (Check, GNU Autounit or CUnit), but this frameworks are very complex so I don't like them. Part of the Unix philosophy is the KISS principle ( Keep It Simple Stupid), so I prefer a simple solution that I could use in a limited platform as MSX is (remember only 64K RAM addressable). In the other side I found MinUnit, a minimal framework (It is only 4 code lines!!!!), and I thought it can be a good beginning point for my own framework.

All test are based in assertions that must be true, so this idea can be easy chained to the secure programming based in assert macro. ANSI C assert macro tests the expression passed as parameter and if it is evaluated to false, an error message is printed in stderr and abort is called. Almost people think that after this your program will finish, but this is not true, due to when abort is called, a SIGABRT is raised, and you can set your own signal handler for it. So we have to capture this signal and  jump to a known point after an assert is false. It's easy understand we need setjmp and longjmp functions.

The setjmp and longjmp functions are commonly said that are not a good programming style because cause spaghetii code (for example in longjmp linux man page it is said), but people often forget that they are used a lot in moderm Object Oriented programs, but they call them exceptions. An exception is only an stacked longjmp solution, so there is a place for them in good code ;).

setjmp sets a point which can be restored using longjmp, but it does something else, returns a value. This value will be 0 when the function is called due to the usual way of the program, but if the program calls longjmp, setjmp returns other value, so you can used this like test result.

You can see the code here: 


#ifndef UTEST_H_
#define UTEST_H_

#ifdef NDEBUG
#undef NDEBUG
#endif

#include  <signal.h>
#include <setjmp.h>
#include <stdio.h>
#include <assert.h>

int utst_actual_test;
int utst_test_ok;
jmp_buf utst_jmp;


static void utst_sigabrt(int dummy)
{
  longjmp(utst_jmp, 1);
}


#define run_test(message, test)  do {                           \
    printf("Test %d %s ... ", utst_actual_test, message);       \
    signal(SIGABRT, utst_sigabrt);                              \
    ++utst_actual_test;                                         \
    if (!setjmp(utst_jmp)) {                                    \
      test;                                                     \
      puts("OK");                                               \
      ++utst_test_ok;                                           \
    } else {                                                    \
      puts("FAILED");                                           \
    }                                                           \
    fflush(stdin);                                              \
  }while (0)



#define run_end() do {                          \
  if (utst_actual_test == utst_test_ok)         \
    puts("ALL TEST PASSED");                    \
  else                                          \
    printf("FAILED %d TESTS\n",                 \
           utst_actual_test - utst_test_ok);    \
  utst_test_ok = 0;                             \
  utst_actual_test = 0;                         \
  } while (0)




#endif /* UTEST_H_ */


So you only have to include this file in the site where you want to execute your test (see that  sigabrt is defined as static, so it is a local function and you can define it in each file without problem of duplicate names).

Now we only have to design our test. For example:



#include "utest.h"


int sum(int i, int j)
{
     assert(i != j);
     return i + j + 3;
}



int main(void)
{

  run_test("Sum of possitive numbers:", assert(sum(2, 1) > 0));
  run_test("Sum of negative numbers:", assert(sum(-1, -2) < 0));
  run_test("Sum of zero values:", assert(sum(0, 0) < 0));
  run_end();
  return 0;
}

If we execute this code we will get the next output:


Test 0 Sum of possitive numbers: ... OK
test: main.c:17: main: Assertion `sum(-1, -2) < 0' failed.
Test 1 Sum of negative numbers: ... FAILED
test: main.c:7: sum: Assertion `i != j' failed.
Test 2 Sum of zero values: ... FAILED
FAILED 2 TESTS


You can see that tests 0 and 2 failed and you can  the assertion failed.  With this macros we have a good unit test framework that we can use without be worried about memory consuptiom.

No TrackBacks

TrackBack URL: http://www.shike2.com/cgi-bin/movabletype/mt-tb.cgi/2

Leave a comment

About this Entry

This page contains a single entry by Roberto Vargas published on December 1, 2010 4:42 PM.

Beginning was the previous entry in this blog.

Emacs configuration is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.