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.