aboutsummaryrefslogtreecommitdiff

Unity test runner

A mostly-auto-discovering unity unit test runner

The excellent unity unit testing library leaves the implementation of the test runner program to the user. This is a nice way to make the library more portable and generic, but it means that anyone as lazy as me is left running one of the bundled Ruby scripts to generate a test runner. I don't like the idea of mandating Ruby as a dependency for unit testing some embedded software, so I decided to make an equally "auto-discovering" method of running unit tests across multiple suites.

So unity-test-runner was born.

Theory of operation

In stock unity, without using the auto-generator ruby scripts, you need to keep updating a list of the tests you would like to run, often separate from the tests themselves. This is error prone and no doubt one day you will accidentally leave a test missing from this list.

In order to use unity-test-runner, it is linked into the same executable as the tests in a suite. It provides the int main, which will iterate through a list of unit tests to run, and run each in sequence with unity.

In order to let the magic discovery happen, you need to cast a spell. Instead of declaring tests as plain functions as you would under stock unity, we must "decorate" them with a macro:

#include "unity.h"
#inlcude "test_runner.h"

RUNNER_DECLARE_TEST(test_one_is_not_zero)
{
    TEST_ASSERT_NOT_EQUAL(0, 1);
}

...and use a custom linker script after compiling tests modules, unity, and this package into objects:

cc -o -Wl,-T,unity-test-runner/linker_list.ld test_runner test_runner.o unity.o test_foo.o ...

Running the resulting executable should show test_one_is_not_zero run and pass:

$ ./test_runner
Suite has 1 tests
test_foo.c:4:test_one_is_not_zero:PASS

-----------------------
1 Tests 0 Failures 0 Ignored
OK

The magic trick is simply that RUNNER_DECLARE_TEST along with linker_list.ld declare a so-called linker list residing in the symbol table of the test runner executable. This list is picked up by the test runner int main which iterates over the whole list and runs each test in sequence.

Notice that this requires no secondary list of test functions to be explicitly maintained, and no function prototypes for each of the unit tests to be declared in the test runner. If you look at test_runner.c you will see how it is very plain (naive?) and portable between different suites and projects.