Zettelkasten
Wonko's collection of notes

Posted on , updated on , in Programming, tagged with ,

Don't Repeat Yourself While Writing Unit Tests in C

A File containing unit tests can be as simple as this:

#include <test.h>
#include <sum.h>

TEST_CASE(sum_zero_elements)
{
    uint32_t elements[1] = {1};
    s = sum(elements, 0);
    ASSERT_EQUAL(s, 0);
}
TEST_CASE(sum_one_element)
{
    uint32_t elements[1] = {1};
    s = sum(elements, 1);
    ASSERT_EQUAL(s, 1);
}
TEST_CASE(sum_null)
{
    s = sum(NULL, 1);
    ASSERT_ERROR(ARGUMENT_ERROR);
}

There is no reason to write any Test Suite functions or Test Runners. The macro in cooperation with the linker can do that, with something like:

#define TEST_CASE(tc)
   static void TC_##tc(TEST_RUN* run);
   __attribute__(("section=.test_cases."##tc)) static const TEST_CASE_INFO TCI_##tc = {
    .line=__LINE__,
    .file=__FILE__,
    .name=#tc,
    .function=TC_##tc,
  }
  static void TC_##tc(TEST_RUN* run)

This places an info struct in a dedicated section. The linker will link all those together to produce an array of TEST_CASE_INFO which the test runner can simply iterate over:

TEST_CASE_INFO __test_cases_start[];
TEST_CASE_INFO __test_cases_end[];

uint32_t passed, failed;
TEST_CASE_INFO tc;

for (tc=&__test_cases_start[0]; tc<&__test_cases_end[0]; tc=&tc[1]) {
    TEST_RUN run = {
       .failed=false,
       .test_case=tc,
    };
    tc.function(&run);
    if (run.failed) {
        failed += 1;
        /* info already printed by assert */
    } else {
        passed += 1;
        printf("%s:TC_%:%d:PASS\n", tc->file, tc->name, tc->line);
    }
}
printf("\nSUMMARY\n%d Tests Passed %d tests Failed\n", passed, failed);

The benefits of this kind of approach:

If you get a bad feeling whether all tests ran, you can do the following things (all of which are a good idea anyway):