Quick Unit testing with C

throwtheswitch

Unit testing is something that lots of developers are used to, and in environments like Python or Java can be even bundled in some awesome libraries. Easy to use, easy to implement, great results.

Then it comes C Embedded: The environment is different, even the people is different!!. Praising the benefits of unit testing is not that easy sometimes, even writing the unit test functions is not as straigtforward.

In this post, I’ll talk about Unity, a small unit test library for C.

Why Unity?

There are a lot of unit test “frameworks” or “libraries” for C.

Its selling points, from the same website:

It’s always funny how people throw adjectives like crazy to a product. I want to add a note to simple. Unity is simple to understand, nevertheless the tests grow convoluted sometimes.

Since there’s a big videogames IDE named unity, finding specific information of this unit test library via search engine is sometimes difficult 😛

Only Unity?

Well. Unity by itself is a set of MACROS and FUNCTIONS with pretty printing of assertions and error/pass counts.

It works, and it’s quite direct. Also, even if I have not tried it yet, you could theorically build a simple test with a cross-compiler and run it in your hardware, printing results via UART.

At the end of the day, you’ll end up coupling with stuff like CMock, a Makefile (Rakefile, cmakefile or whatever). And once the mocks kick in, it can get strange and cause fear like Cthulhu. Just look at the Makefile documentation for unity. fortunately the guys at throw the switch are as “special” as we are, and their documentations and courses are fun to read/listen.

Pointing to a ruby build system, is not incidental

A basic let’s go!

On a simple non-dependant module, Unity shines. Include the library, write some simple tests and go. I present here an example of a weird adding function.

/**@brief This function adds two 
          numbers and sets the error variable 
          to 1 if overflow happened
*/
uint32_t myWeirdAdd(uint32_t a, uint32_t b, uint8_t* error)
{
    *error = (a > UINT32_MAX - b);
    return a + b;
}

We’ll want to test two things. Basic functionality (Addition of two integers), and overflow. Let’s see how we would do that with Unity:

#include "unity.h"
#include "func.h"

/**@brief test that basic add works, and returns error code of 0*/
void test_BasicAdditionShouldWork(void)
{
    uint8_t err;
    TEST_ASSERT_EQUAL(30, myWeirdAdd(10, 20, &err));
    TEST_ASSERT_EQUAL(0, err);
}

/**@brief test that the overflow is detected, and returns error code of 1*/
void test_BasicOverflowIsDetected(void)
{
    uint8_t err;

    myWeirdAdd(0xFFFFFFFF, 20, &err);
    TEST_ASSERT_EQUAL(1, err);
}


/**@brief Main program to be executed*/
int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_BasicAdditionShouldWork);
    RUN_TEST(test_BasicOverflowIsDetected);
    return UNITY_END();
}

This is as simple as it gets:

The test functions begin with test_ by convention, we can name it as we like though.

Each test runs the function with different parameters, and ASSERTS that the output is the one that we expect. This special asserts are the ones that will generate a nice stdout output on the command line, once we have a lot of tests that output is very valuable.

Once compiled, we’ll have our test ready:

gcc unity.c func.c test.c -o test

./test

$ ./a.exe
test.c:26:test_BasicAdditionShouldWork:PASS
test.c:27:test_BasicOverflowIsDetected:PASS

-----------------------
2 Tests 0 Failures 0 Ignored
OK

Conclusions

In this post I’m guilty of the basic sin of “the easy example”. Over my time as an engineer I’ve seen a lot of examples on books and websites that go like this:

  • Let’s start with building a wheel
  • here you can see an example of building a wheel
  • Now you know how to build a wheel, go and build your own car!

The basic step is always easy, the devil is in the details :-D. So I have no excuse. The difficult part of the unit testing (not taking into account the design of the tests themselves) in C is two-fold:

  • Mocks
  • Pointers!!

Mocking is an interesting part by itself. The main idea is to control dependencies between modules by creating a dummy module. That module will simply check that is called under specific circumstances. But of course, you have to tell it what circumstances. With all that, you end with long tests with lots of boilerplate things. All this boilerplate is actually checking that everything goes fine and dandy, but it’s not as clear as the TEST_ASSERT used in my example.

Check this longer example using Cmock and Unity.

void test_projXsetStuff_normalFlow(void)
{
    uint8_t setStuff, retVal;
    uint8_t dataSizeReturn;
    dispatcherFunction function = (dispatcherFunction)projXstuffFunction;

    setStuff = 1;

    //Assume that our GPIO is ready to go
    //Note that this is a MACRO, hiding a complexity of 4 functions underneath
    TEST_CMOCK_READ_REG_EXPECT(GPIO_ISGOODTOGO, GPIO_VALUE, 1, 1);

    //Check that timer has expired
    tmrIsExpired_ExpectAndReturn(TMR_2, TRUE);

    //finally the stuff will be set to one
    TEST_CMOCK_WRITE_REG(GPIO_STUFF, GPIO_VALUE, 1);

    //TMR_1 keeps STUFF on
    tmrSetCallbackFunction_ExpectAndReturn(TMR_1, NULL, NOERROR);
    tmrSetCallbackFunction_IgnoreArg_callbackFunction();
    tmrSetTimeout_ExpectAndReturn(TMR_1, TMR_1_TIMEOUT_VAL, NOERROR);
    tmrResetTimer_ExpectAndReturn(TMR_1, NOERROR);
    tmrStartTimer_ExpectAndReturn(TMR_1, NOERROR);

    //TMR_2 counts how long before STUFF can be ReActivated
    tmrSetTimeout_ExpectAndReturn(TMR2, TMR_2_TIMEOUT_VAL, NOERROR);
    tmrResetTimer_ExpectAndReturn(TMR2, NOERROR);
    tmrStartTimer_ExpectAndReturn(TMR2, NOERROR);


    //Another helper function. Filling an input buffer with what is expected
    fillInBuffer(1, setStuff);

    retVal = function(dataBufferIn, dataBufferOut, &dataSizeReturn);

    TEST_ASSERT_EQUAL(0, retVal); //No error happened
    TEST_ASSERT_EQUAL(0, dataSizeReturn); //The return data size payload is zero
}

Pointers are in general more or less gracefully handled by Cmock. But as far as I’ve seen it’s a source of confusion with test writers and readers alike.

Closing comments? Unit testing C is possible, but you need: a good infrastructure, a good project organization, an easy to use build process (+ at least somebody understanding all the runes involved), good module isolation (damn, that’s not the common thing in the Embedded projects where I landed), and the stars aligned. It may be a slow process, but the payback can be worth it.

I say so from the point of view of a guy that used Python. You don’t need anything special, unit test is there free, and it matches easily with OO design. Which is not the case with C.

References

Unity ⇒GO
Cmock ⇒GO

Advertisement

Leave a comment

Filed under code, tips

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.