Home > Net >  Set Up Development Environment for Unit Testing C
Set Up Development Environment for Unit Testing C

Time:11-28

I'm new to unit testing and have mostly programmed using IDEs, therefore I haven't created and/or modified makefiles before.

Now that I'm exploring Unit Testing and TDD in general; I'm not sure how to set up the development environment so that my unit tests automatically run on every build.

Please help. A general procedure to achieve this would do wonders.

I have not tried anything yet as I'm not very familiar with modifying C Make files.

CodePudding user response:

Here is a simple Makefile I use for small TDD projects using criterion:

CC=gcc
RELEASE_CFLAGS=-ansi -pedantic -Wall -Werror -Wextra
TESTS_CFLAGS=-pedantic -Wall -Werror -Wextra
TESTS_LDFLAGS=-lcriterion

RELEASE_SRC=$(shell find src/ -type f -name '*.c')
RELEASE_OBJ=$(subst src/,obj/,$(RELEASE_SRC:.c=.o))

TESTS_SRC=$(shell find tests/src/ -type f -name '*.c')
TESTS_OBJ=$(subst src/,obj/,$(TESTS_SRC:.c=.o))
TESTS_BIN=$(subst src/,bin/,$(TESTS_SRC:.c=))

default: run-tests

obj/%.o: src/%.c
    $(CC) $(RELEASE_CFLAGS) -c $^ -o $@

tests/obj/%.o: tests/src/%.c
    $(CC) $(TESTS_CFLAGS) -c $^ -o $@

tests/bin/%: tests/obj/%.o $(RELEASE_OBJ)
    $(CC) $(TESTS_LDFLAGS) $^ -o $@

# prevent deleting object in rules chain
$(TESTS_BIN): $(RELEASE_OBJ) $(TESTS_OBJ)

run-tests: $(TESTS_BIN)
    ./$^ || true

clean:
    rm -f $(RELEASE_OBJ) $(TESTS_OBJ)

clean-all: clean
    rm -f $(TESTS_BIN)

It compiles production code with C89 (-ansi) and tests code with unspecified standard. Files in src/ are moved to obj/, same thing for tests/src/ and tests/obj/. Tests binaries (AKA test suites) depends on every source files and are included in each test binary, making them bigger but it's not a problem for small projects. If binaries size is an issue, you'll have to specify which object to include for each binary.

Directory structure is made with this command:

mkdir -p src obj tests/{src,obj,bin}

An exemple test file:

#include <criterion/criterion.h>
#include "../../src/fibo.h"

Test(fibonacci, first_term_is_0)
{
    // given
    int term_to_compute = 0;

    // when
    int result = fibonacci(term_to_compute);

    // then
    cr_assert_eq(result, 0);
}

Test(fibonacci, second_term_is_1)
{
    // given
    int term_to_compute = 1;

    // when
    int result = fibonacci(term_to_compute);

    // then
    cr_assert_eq(result, 1);
}

And the associated production code:

#include "fibo.h"

unsigned long fibonacci(unsigned int term_to_compute)
{
    return term_to_compute;
}

As you can see, production code is quite dumb and it needs more tests, because it only meets specified requirements (unit tests).

EDIT: Check the Make documentation to learn more about syntax, builtin functions, etc. If you want to learn more about TDD, YouTube has a lot to offer (live codings, explanations, TDD katas): check Robert C Martin (Uncle Bob), Continuous Delivery channel, etc.

PS: returning a long is not the best option here, you could want fixed size integers to have same result on different platforms, but the question was about "how-to TDD". If you're new to TDD, writing the given/when/then might help. Write tests first, and think about edge cases (like, specify overflow ?). I use similar setup when doing NASM TDD and testing with C.

  • Related