Modern unit testing in C with TDD and Ceedling

October 07, 2016

Matt Chernosky-October 07, 2016

Back when I first tried to unit test some code, I didn't really get it.

A colleague of mine was really excited about unit testing, and suggested that I should try it on my project. So I wrote some code and picked out a shiny, new unit test framework.

I did manage to write a few tests, but it was painful. At the time, writing the code itself was challenging enough let alone figuring out how to write tests for it. In addition, each test needed extra boilerplate code to get it running, and I had an awkward setup with #ifdefs to control the running of the tests.

And I wasn't particularly convinced these tests were worth the effort.

Since that time though, I've come to value unit testing as critical to the way I develop embedded software.

What changed my mind? Well looking back, I've since discovered a few things that were missing from my original approach. I'll get to these in bit, but first let's make sure we're on the same page.

What's a unit test?
A unit test is just some code that calls some other code, used to test that it behaves as you expect:

void this_is_a_unit_test(void) {

      int next = get_next_fibonacci(5);

      ASSERT_EQUAL(next,8);

}

 

In this example we're testing the get_next_fibonacci function. We call the function with an input of 5 and we expect to get an 8 back.

The get_next_fibonacci function is likely part of a module of code with other Fibonacci-related functions. In this case the Fibonacci module is the unit under test.

This test verifies that our get_next_fibonacci function does what we want, and it doesn't require us to run the entire application to do so. We just execute this unit test function and get the results.

The unit tests can be run during development of the Fibonacci module to make sure we did it right, and then at any later time (like when we make changes to the code) to make sure we haven't broken anything.

What's a unit test framework?
A unit test framework is just some code that makes it easier to run and record the results of unit tests.

What was that ASSERT_EQUAL(next, 8) in the example above? It's a macro I just invented for comparing two integer values – but it's the sort of thing you'd expect to have with a unit test framework. If next == 8 the test will pass and if not the test will fail.

There is also usually a way to run each test like: 

void main(void) {

      RUN_TEST(this_is_a_unit_test);

}

  

When the test runs, this will print the results of test. If a test fails, we'll typically get more specific information about the failure like a line number.

There are usually a few other features (like fixtures or suites with set up and tear down code) but this is pretty much all there is to a unit test framework.

There are many, many unit test frameworks available for C. In fact, it's actually easy to write a simple one for yourself. A few popular ones are Unity, CppUTest, and GoogleTest.

But this is where I started my unit testing adventure. I knew I wanted to unit test so I needed a unit test framework, right? Well it turns out that a unit test framework makes it a little easier to write unit tests... but that's not the end of the story.

Testing real code
Most real code is not like the over-simplified examples found in articles on the Internet. It's certainly not like that earlier Fibonacci example. Real code calls other functions, and real embedded code interacts with the hardware.

For (a slightly less simplified) example, imagine a function that updates an LED display to show our Fibonacci numbers: 

void update_display(int previous) {

      int next = get_next_fibonacci(previous);

      led_display_set_number(next);

}

  

Armed only with my unit test framework, I didn't know how to test this. In this case, our update_display function in the Display module depends on both the LED module and the Fibonacci module:


Figure 1. A module with dependencies. (Source: Author)  

This function takes no arguments and returns no value, so we can't test it like get_next_fibonacci. How do we know when this is working correctly? It's working when led_display_set_number is called with whatever value is returned from get_next_fibonacci.

How do you test these sorts of interactions with other functions and modules? This was my first discovery: mocks.

Continue to page 2 >>

 

< Previous
Page 1 of 3
Next >

Loading comments...