20

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( ... );
}
MarkP
  • 4,168
  • 10
  • 43
  • 84
  • [Possible duplicate](http://stackoverflow.com/questions/65820/unit-testing-c-code), if not just related. – ajp15243 Jun 06 '13 at 19:00
  • @ajp15243: I'm less interested in what framework or tool to use when unit testing. Rather, I'm interested in ways to write test for C code that's high dependent or uses common C idioms, such as a `global_context_t` struct that gets passed around. – MarkP Jun 06 '13 at 19:02
  • Well I'd edit out the 'duplicate' part if I could at this point. Thought you might at least find the frameworks helpful, but oh well :/. – ajp15243 Jun 06 '13 at 19:11
  • 5
    The short answer is you have to think C when you write C code and unit tests. Don't try to use C as C++ (nor the other way around, for that matter), you just can't. –  Jun 06 '13 at 20:19
  • 3
    hmmm. i'd say the short answer is that's pretty crappy c code. c programmers should write code for tests just like everyone else. – andrew cooke Jun 06 '13 at 23:17
  • I've got a gut feeling that this might be something to do with Function pointers. I'm not prepared to write a full answer on that though, it's been too long since I've done any real C. Good luck! – Russ Clarke Jun 06 '13 at 23:49
  • Many people add some ugly `#define DEBUG ...` that show all over the code. – Déjà vu Jun 07 '13 at 00:14

3 Answers3

15

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.

Community
  • 1
  • 1
Timothy Jones
  • 21,495
  • 6
  • 60
  • 90
9

That was a very good question designed to lure people into believing that C++ is better than C because it's more testable. However, it's hardly that simple.

Having written lots of testable C++ and C code both, and an equally impressive amount of untestable C++ and C code, I can confidentially say you can wrap crappy untestable code in both languages. In fact the majority of the issues you present above are equally as problematic in C++. EG, lots of people write non-object encapsulated functions in C++ and use them inside classes (see the extensive use of C++ static functions within classes, as an example, such as MyAscii::fromUtf8() type functions).

And I'm quite sure that you've seen a gazillion C++ class functions with too many parameters. And if you think that just because a function only has one parameter it's better, consider the case that internally it's frequently masking the passed in parameters by using a bunch of member variables. Let alone "static or hidden" functions (hint, remember that "private:" keyword) being just as big of a problem.

So, the real answer to your question isn't "C is worse for exactly the reasons you state" but rather "you need to architect it properly in C, just as you would in C++". For example, if you have dependent functions, then put them in a different file and return the number of possible answers they might provide by implementing a bogus version of that function when testing the super-function. And that's the barely-getting-by change. Don't make static or hidden functions if you want to test them.

The real problem is that you seem to state in your question that you're writing tests for someone else's library that you didn't write and architect for proper testability. However, there are a ton of C++ libraries that exhibit the exact same symptoms and if you were handed one of them to test, you'd be just as equally annoyed.

The solution to all problems like this is always the same: write the code properly and don't use someone else's improperly written code.

Wes Hardaker
  • 21,735
  • 2
  • 38
  • 69
  • Home run. +1 for telling it like it is. – Randy Howard Jun 07 '13 at 01:42
  • Thank you @RandyHoward, I appreciate the compliment greatly. – Wes Hardaker Jun 07 '13 at 01:42
  • Great advice, I also upvoted this. I think C is less forgiving about adding tests after the fact than some other languages, which leads to a bad reputation. But, you can *absolutely* write clean, testable C. – Timothy Jones Jun 07 '13 at 02:10
  • Thanks @TimothyJones. I agree, you can definitely write C well the first time. And I'll admit that there are elements of C++ that make it easier to write tests. Sure, anything that exists where 1 thing is greater than or equal to the functionality of the other means one will triumph in certain areas. So yes, it would be easier to write some tests for C++ if the classes are well constructed. But *both* languages must be well constructed for either to be testable. – Wes Hardaker Jun 07 '13 at 02:21
  • 2
    Absolutely. Personally, I'd far rather be faced with untestable C than untestable C++ :) – Timothy Jones Jun 07 '13 at 02:25
  • What's even worse, @TimothyJones, is *undocumented* C++. C++ adds a wonderful layer of abstraction, but unless it's well documented that really adds up to a wonderful layer of obfuscation. Many C++ libraries suffer from this and can't understand why no one will reuse their code. – Wes Hardaker Jun 07 '13 at 03:14
1

When unit testing C you normally include the .c file in the test so you can first test the static functions before you test the public ones.

If you have complex functions and you want to test code calling them then it is possible to work with mock objects. Take a look at the cmocka unit testing framework which offers support for mock objects.

asn
  • 798
  • 9
  • 17