Unit Testing Doesn’t Need to Be Involved

I mentioned that I was working on personal project in C the other day. One of the things that I wanted to do was to have unit tests for the parts that made sense. I briefly looked at some C unit testing frameworks… but really, this doesn’t need to be that hard.

I personally like to start things off small and iterate quickly towards a solution. With personal projects, it is very easy to get into the mindset of “well, I have so much time I should just do it ‘right'”. The problem is: right is such a subjective term that completely lacks all context for the solution. For the vast majority of problems, there isn’t a right solution, there is a solution that meets the demands of the problem at hand within the constraints of the project at hand.

All this said, this gets me back to my unit testing “framework” for my project. I really just needed the following:

  1. The ability to run my tests quickly, and
  2. A nice, readable output

I don’t really need any type of filtering mechanism or anything like that. Maybe I will one day, but not now.

Writing a Test Method

So I started with this:

static char *test_method_decl();

The rule is simple: any test method will return NULL if there is no error, otherwise it will return an the message of the error.

Next up, of there there needs to be an assert() method of some sort; the usage looks like this:

static char *test_add() {
  my_assert(2 + 2 == 5, "math is hard!");
}

So, what does my_assert() look like?

#define my_assert(unit_test, message)                         \
    do {                                                      \
        if (!(unit_test)) return __FILE__ ": error:" message; \
    } while (0)

Yep, it’s a macro. Deal with it. =)

Invoking a Test Method

Again, to run tests really easily, I just compile them into an executable:

int main(int argc, char **argv)
{
    int number_of_tests_run = 0;
    int number_of_tests_failed = 0;

    print_test_header();
    run_test(test_add, number_of_tests_run, number_of_tests_failed);
    print_test_footer(number_of_tests_run, number_of_tests_passed);

    return number_of_tests_failed != 0;
}

The last major piece is the run_test macro. It’s a little bit ugly, but it’s just this:

#define run_test(unit_test, test_counter, test_fail_counter)    \
    do {                                                         \
        test_counter++;                                          \
                                                                 \
        char *message = unit_test();                             \
        if (message) {                                           \
            printf(u8"  𐄂 " #unit_test "\n");                    \
            printf(u8"    → error: %s\n", message);              \
            test_fail_counter += 1;                              \
        } else {                                                 \
            printf(u8"  ✓ " #unit_test "\n");                    \
        }                                                        \
    } while (0)

Getting the Test Results

Now, when I run these tests, I’ll get an output that looks something like this:

$ make test
‡ tests from sources/project_name/tests/mytests.c
  𐄂 test_config_data_round_trip_color
    → error: sources/project_name/tests/mytests.c: error:the Green component is incorrect.
  ✓ test_config_data_round_trip_shadow

§ test status (1 of 2) passed.
make: *** [test] Error 1

Now there’s also the header and footer macros:

#define print_test_header() printf(u8"‡ tests from " __FILE__ "\n");

#define print_test_footer(test_counter, test_fail_counter) \
    printf(u8"\n§ test status (%d of %d) passed.\n", test_counter - test_fail_counter, test_counter);

That’s about it! Is it fully featured? No, but does it need to be?

Unit Testing Doesn’t Need to Be Involved