Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing unit tests for C code

Tags:

c

unit-testing

I'm a C++ developer and when it comes to testing, it's easy to test a class by injecting dependencies, overriding member functions, and so on, so that you can test edge cases easily. However, in C, you can't use those wonderful features. I'm finding it hard to add unit tests to code because of some of the 'standard' ways that C code is written. What are the best ways to tackle the following:

Passing around a large 'context' struct pointer:

void some_func( global_context_t *ctx, .... )
{
  /* lots of code, depending on the state of context */
}

No easy way to test failure on dependent functions:

void some_func( .... )
{
  if (!get_network_state() && !some_other_func()) {
    do_something_func();
    ....
  }
  ...
}

Functions with lots of parameters:

void some_func( global_context_t *, int i, int j, other_struct_t *t, out_param_t **out, ...)
{
  /* hundreds and hundreds of lines of code */
}

Static or hidden functions:

static void foo( ... )
{
  /* some code */
} 

void some_public_func( ... }
{
  /* call static functions */
  foo( ... );
}
like image 283
MarkP Avatar asked Jun 06 '13 18:06

MarkP


1 Answers

In general, I agree with Wes's answer - it is going to be much harder to add tests to code that isn't written with tests in mind. There's nothing inherent in C that makes it impossible to test - but, because C doesn't force you to write in a particular style, it's also very easy to write C code that is difficult to test.

In my opinion, writing code with tests in mind will encourage shorter functions, with few arguments, which helps alleviate some of the pain in your examples.

First, you'll need to pick a unit testing framework. There are a lot of examples in this question (though sadly a lot of the answers are C++ frameworks - I would advise against using C++ to test C).

I personally use TestDept, because it is simple to use, lightweight, and allows stubbing. However, I don't think it is very widely used yet. If you're looking for a more popular framework, many people recommend Check - which is great if you use automake.

Here are some specific answers for your use cases:

Passing around a large 'context' struct pointer

For this case, you can build an instance of the struct with the pre conditions manually set, then check the status of the struct after the function has run. With short functions, each test will be fairly straightforward.

No easy way to test failure on dependent functions

I think this is one of the biggest hurdles with unit testing C. I've had success using TestDept, which allows run time stubbing of dependent functions. This is great for breaking up tightly coupled code. Here's an example from their documentation:

void test_stringify_cannot_malloc_returns_sane_result() {
  replace_function(&malloc, &always_failing_malloc);
  char *h = stringify('h');
  assert_string_equals("cannot_stringify", h);
}

Depending on your target environment, this may or may not work for you. See their documentation for more details.

Functions with lots of parameters

This probably isn't the answer you're looking for, but I would just break these up into smaller functions with fewer parameters. Much much easier to test.

Static or hidden functions

It's not super clean, but I have tested static functions by including the source file directly, enabling calls of static functions. Combined with TestDept for stubbing out anything not under test, this works fairly well.

 #include "implementation.c"

 /* Now I can call foo(), defined static in implementation.c */

A lot of C code is legacy code with few tests - and in those cases, it is generally easier to add integration tests that test large parts of the code first, rather than finely grained unit tests. This allows you to start refactoring the code underneath the integration test to a unit-testable state - though it may or may not be worth the investment, depending on your situation. Of course, you'll want to be able to add unit tests to any new code written during this period, so having a solid framework up and running early is a good idea.

If you are working with legacy code, this book (Working effectively with legacy code by Michael Feathers) is great further reading.

like image 161
Timothy Jones Avatar answered Sep 26 '22 10:09

Timothy Jones