Motivation
So, I am coming back to C programming. This time I am doing some cool stuff on gesture recognition from accelerometer readings :).
This project is meant to be running on a Cortex processor, compiled with an IAR compiler, with some dependencies from this distribution. But I don’t really have yet a license nor time to waste. So i am working with GCC.
The first thing I am dealing with is Testing.
It’s being really a long time since the last time I worked in C exclusively, and it’s evident that there are many things I forgot. Having work with gradle or maven, or in other realms where building is not a real issue, made my C building skills get rusty. and probably this is why i am writing a blog post :).
I tried many unit test frameworks, and I should do my own implementation, since all what I found is way too complex to use.
Quick overview
In this section I will talk quickly about each framework I tried and why I end up discarding or choosing
Google test and Boost tests
Google test is probably my favourite, and Boost is not bad at all. It has a clear and simple way to define tests in a C fashion, without having to care much about the minimal C++ infrastructure needed to run tests. There are, however, two important reasons that discard these framework.
- C++ is not C
- In C code, for reason of clarity I need to define things as bool, or specific types that already exist in C ++. I could deal with it with some defines, but that deals to a really invasive and unpredictable result. Tests should run under exactly same conditions to be trustable
- C++ compiled results has some slight differences with C. There are some assumptions I cannot do in C++, as the memory distribution. And since I am working with a program that needs to be compiled in many platforms, I don’t want to populate C++ problematics taking care of the compiler only for testing support.
- CMake / AutoTools.
- This both building systems are quite annoying, complex, arbitrary and lack of consistence. That makes the curve of learning quite high, and the result is quite arbitrary as well. (Don’t tell me that all those magical variables make any sense. You will never convince me)
- Both building tools outline come with an annoying amount of files. This is a really specific project. After applying a generic CMake for including GTest in my C project, able to compile the test folder with the proper related code I ended up having 3 building files per folder, before compilation. After compilation even more.
Check and CUnit
These both unit test frameworks are interesting but quite bureaucratic. They support the bases for testing, but they lack much usability from the infrastructure point of view (You have to register your self functions into a test suit, and compose your test cases with test suites). You have to have a main per test suit, or having a file the gathers all the tests and knows everything for being able to run. This makes refactoring quite complex, when is supposed to help refactoring.
Despite my complaints, these two frameworks respond to my need of, using only C for compiling and being able to use a simple makefile. I know, it may sound orthodox, old, out of fashion. I don’t care, is only one (really crappy but short) file.
It would be the same to me to use one or the other. I finally choosed Check, because the main seems to be less bureaucratic. That’s all :).
Check
So check has a good enough documentation, but kind of scattered. It’s obvious that the standard C programmer did not arrive yet to the need of modern techniques of quality. Maybe because things are more complex to generally test at this level.
So let me save you some time and show you how to load it and use it in the same place. (Sorry, only for Ubuntu/Debian 🙂 ).
Install check
$ sudo apt-get install check
Add check in your code
#include<check.h>
#include<yourheader.h>int var;
/** Setup method. Will be configured to run before each test is executed */
static void setUp {
var = 1;
}
/** Teardown method. Will be configured to run after each test is executed */
static void tearDown {
var = 0;
}/** Unit test method. Will be configured into a test case */
START_TEST(YourTestName) {
ck_assert_int_eq(var, 1);
}/** Create suit method. Creates a test suite, that contains test cases inside. Mean to be executed with a test runner */
Suite * createSuite(void) {
Suite *suite;
TCase *test_case;
/* Creates a suite type */
suite = suite_create(“suit-name”);
/* Creates a test-case type */
test_case = tcase_create(“test-case-name”);
/* Register a unit test into the test case. You should do this for each unit testfunction you develop */
tcase_add_test(test_case, OneDimensionPeakDetector_Executes);
/* Registers the setup and teardown functions for this test case */
tcase_add_unchecked_fixture(test_case, setUp, tearDown);
/* It registers the test case into the test suite */
suite_add_tcase(suite, test_case);
return suite;
}int main(void) {
int number_failed;
Suite *suite;
SRunner *testrunner;/* It creates the suite with the function above */
suite = createSuite();
/* It a suit-test runner */
testrunner = srunner_create(suite);
/* It executes the tests */
srunner_run_all(testrunner, CK_NORMAL);
/* Checks failures */
number_failed = srunner_ntests_failed(testrunner);
/* Release memory */
srunner_free(testrunner);
/* Informs the status */
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;}
Compiling
For compiling with gcc and check you have to add many library flags that are not quite well documented
Normally it should be enough to add them at the very end of your GCC compilation line:
-lcheck_pic -pthread -lrt -lm
Makefile
This is far from being a good model of a makefile. Is quite simplistic and it does not cover many problematics, but for this project is far enough. And, if your project is simple as mine, you may find this awful makefile good enough.
This makefile supposes that you are in a folder with a build folder and a src folder, where you have everything, maybe in subfolders, by example:
src
├── Collection
│ ├── include
│ │ └── Collection.h
│ ├── src
│ │ └── Collection.c
│ └── test
│ └── collection_tests.c
├── Core
│ ├── include
│ │ └── lib.h
│ ├── src
│ │ ├── lib.c
│ │ └── main.c
│ └── test
└──── core_tests.c
.DEFAULT_GOAL := all
CC=gcc
CFLAGS=-I./src/Collection/include -I./src/Core/include -lcheck_pic -pthread -lrt -lm
ODIR=build### This variable is build taking all the .c files with it respective folder’s name. It avoid all the main.c and files finished by _tests.c. This is because each of this files will have a main function.
SRC=$(shell find . -iname *.c -not \( -iname main.c -or -iname *_tests.c \))### This variable takes all the generated files in SRC variable and replace .c by .o
OBJ=$(shell echo $(SRC) | sed ‘s/\.c/\.o/’ | sed ‘s/src/build/’ )$(ODIR)/%.o: src/%.c
mkdir -p $(dir $@)
$(CC) -c -o $@ $< $(CFLAGS)$(ODIR)/main: $(OBJ)
gcc -o $@ $^ src/Core/src/main.c $(CFLAGS)$(ODIR)/core_tests: $(OBJ)
gcc -o $@ $^ src/Core/test/core_tests.c $(CFLAGS)$(ODIR)/collection_tests: $(OBJ)
gcc -o $@ $^ src/Collection/test/collection_tests.c $(CFLAGS).PHONY: clean all default
default: all
clean:
rm $(ODIR)/* -rfall: clean $(ODIR)/main $(ODIR)/core_tests $(ODIR)/collection_tests
This makefile will output all the compilations into a build folder that, as i said before, it must exists before executing the makefile.
Again, this makefile is quite simplistic and is far from being elegant. But it works.
I hope this gives some light to your problems with testing in C.