cunit-testingcmocka

Unit test run configuration


I need to get up and running with Cmocka unit testing framework. My setup is:

src/math/addition/add.c (+add.h)

int add(int a, int b) {return a + b;}

src/math/subtraction/sub.c (+sub.h)

int sub(int a, int b) {return a - b;}

Makefile

VPATH := src src/math src/math/addition
CFLAGS += -Isrc -Isrc/math -Isrc/math/addition
all: libMath clean

libMath: add.o sub.o
    ar rcs bin/libMath add.o sub.o

clean:
    rm -rf *.o

%.o: %.c %.h

Unit Tests

test/math/addition/add_test.c

#include "../src/math/addition/add.h"

void test_add() {
     assert(add(4, 5), 9);
}

test/math/subtraction/sub_test.c

#include "../src/math/subtraction/sub.h"

void test_sub() {
    assert(sub(9, 5), 4);
}

test/math/addition/add_test.c (from cmocka.org)

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

/* A test case that does nothing and succeeds. */
static void null_test_success(void **state) {
    (void) state; /* unused */
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(null_test_success),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}

I'm new to unit testing in C and basically can't get my head around to setup the unit tests including linking the Cmocka library etc.

My idea is to have several unit test files instead of putting all unit tests in one file.

Edit based on Clearer's answer

Scaling up

Going from 1 test file to 2 and 3 and it will be at least 10+ files. Looking for some optimizations and articulations to scale up nicely and for easy management. Here's what I've so far.

VPATH := src/math/add  src/math/sub  src/math/mul # split src/test path
VPATH += test/math/add test/math/sub test/math/mul

all: libMath clean

libMath: add.o sub.o mul.o
    ar rcs bin/libMath add.o sub.o mul.o # suggestion? $^

test: add_test sub_test mul_test clean
    ./add_test
    ./sub_test
    ./mul_test

add_test: add_test.o add.o
    $(CC) -o $@ $^

sub_test: sub_test.o sub.o
    $(CC) -o $@ $^

mul_test: mul_test.o mul.o
    $(CC) -o $@ $^

clean:
    $(RM) *.o

%.o: %.c %.h

Here's the observation so far.

  1. The pattern seems to be like adding a new target for each couple of test and src files.
  2. Adding the .o object to the libMath both in the prerequisites and the command
  3. adding the test executable under test: target in the prerequisites and the command

While scaling up, Is it better this way or there could be better approaches?

P.S. I've removed CFLAGS line, it's working fine without it, helped me to clean up and reduce some clutter. is it ok? My IDE (clion) shows red wiggly lines if the path isn't right to .h files so I'm using full paths in the test files to include src files.

P.P.S It creates test executables on the root of the project, how to have all the binaries created in the bin folder and then delete all at the end of the project.


Solution

  • I would add a test target. That target would depend on all your test programs and should then execute the programs; you may want to add individual targets to execute the programs and just keep one master test target to make sure all of them are executed. Each test program would depend on the object files that are required for the test; if you're doing an addition test, let the addition test depend on the addition.o and add_test.o. Link them as you would always do and then execute them.

    Example:

    test: addition_test
       ./addition_test
    
    addition_test: add_test.o add.o
        $(CC) -o $@ $^
    

    Scaling tests

    You can scale your tests by adding two rules and deleting most of the other rules, related to testing:

    test: add_test_run sub_test_run
    
    %_run: %
        ./$<
    
     %_test: %.o %_test.o
        $(CC) -o $@ $^
    

    Should do everything you want. This allows running tests in parallel; you can avoid running tests that doesn't need to be run by creating a file at the end of each run, say: a log file that tells you the result of the test run.

    This should do the trick:

    test: add_test.log sub_test.log
    
    %.log: %
        ./$^ > $@
    
     %_test: %.o %_test.o
        $(CC) -o $@ $^
    

    You should use $(RM) instead of rm -rf in your clean target. $(RM) is platform independent while rm -rf only works on UNIXy platforms.