Aan de slag met CppUTest

Aan de slag met CppUTest

Aan de slag met CppUTest

May 14, 2017

Gepubliceerd door

Gepubliceerd door

Bird

Bird

-

Categorie:

Categorie:

E-mail

E-mail

Ready to see Bird
in action?

Ready to see Bird
in action?

Getting Started with CppUTest

Bij SparkPost steken we veel tijd en moeite in het testen van onze code. Ons platform is geschreven in C, en onlangs onderzocht ik de integratie met een unit testing framework genaamd "CppUTest", dat xUnit-achtige tests biedt voor C/C++. Dit framework is robuust, rijk aan mogelijkheden, en actief in ontwikkeling, waardoor het een goede keuze is. Het biedt ook een C integratie laag waardoor het gemakkelijk te gebruiken is met onze platform C code, ook al is het grootste deel van het framework C++. Deze tutorial laat zien hoe je met CppUTest aan de slag kunt in je eigen projecten.


CppUTest downloaden

De CppUTest project page is available here, and the repository is on github. It’s also included in the package management repositories for many linux distros, as well as homebrew on Mac OS. De examples that follow were executed on Mac OS X, but they’re derived from code written for Red Hat, the OS our platform runs on.

The basics are well documented on CppUTest’s startpagina. We’re going to breeze through that and get to some of the more interesting features.


De basis leggen

Eerst maar eens wat code schrijven!

Ons testproject zal een 'main'-bestand hebben en een utility-bibliotheek genaamd 'code' bevatten. De bibliotheek zal een eenvoudige functie bieden die (voorlopig) 1 teruggeeft. De bestanden worden als volgt ingedeeld:

├── src │ ├── code.cpp │ └── code.h │ └── main.cpp └── t ├── main.cpp └── test.cpp

Laten we beginnen met het schrijven van de src/ bestanden

// src/main.cpp #include <stdlib.h> #include <stdio.h> #include "code.h" int main(void) { test_func(); printf("hello world!\n"); exit(0); }

// src/code/code.cpp #include <stdlib.h> #include "code.h" int test_func () { return 1; }

// src/code/code.h #ifndef __code_h__ #define __code_h__ int test_func (); #endif

Now, let’s do the tests, which will live in the t/ directory.  The first thing to do is to set up a test runner which will run our test files. This is also the ‘main’  function that will execute once this is all compiled:

// t/main.cpp #include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av) { return CommandLineTestRunner::RunAllTests(ac, av); }

Nu kunnen we onze eerste testmodule schrijven:

// t/test.cpp #include "CppUTest/TestHarness.h" #include "code.h" TEST_GROUP(AwesomeExamples) { }; TEST(AwesomeExamples, FirstExample) { int x = test_func(); CHECK_EQUAL(1, x); }

Next, we need to write makefiles.  We’ll need two: one for the project files under src/, and one for the tests.


Project Makefile

De makefile van het project staat op hetzelfde niveau als de 'src' en 't' directories in de root van het project. Het zou er zo uit moeten zien:

# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=voorbeeld TEST_DIR=t test: make -C $(TEST_DIR) test_clean: make -C $(TEST_DIR) clean code.o: gcc -c -I$(CODE_DIR) $(CODE_DIR)/code.cpp -o $(CODE_DIR)/code.o main: code.o gcc -I$(CODE_DIR) $(CODE_DIR)/code.o $(SRC_DIR)/main.cpp -o $(OUT) all: test main clean: test_clean rm $(SRC_DIR)/*.o $(CODE_DIR)/*.o $(OUT)

Note that this uses ‘make -C’  for the test targets – meaning that it will call ‘make’  again using the makefile in the test directory.

Nu kunnen we de 'src'-code compileren met de makefile en zien dat het werkt:

[$ make main gcc -c -I./src/code ./src/code/code.cpp -o ./src/code/code.o gcc -I./src/code ./src/code/code.o ./src/main.cpp -o voorbeeld []$ ./voorbeeld hello world!


Tests Makefile

Voor de tests is het iets ingewikkelder, omdat we de CppUTest-bibliotheek moeten laden en integreren.

De CppUTest repository levert een bestand genaamd "MakefileWorker.mk". Het biedt veel functionaliteit die het bouwen met CppUTest eenvoudig maakt. Het bestand leeft onder de "build" directory in de git repository. Voor deze tutorial gaan we ervan uit dat het gekopieerd is naar de 't/' directory. Het kan als volgt worden gebruikt:

# we willen geen relatieve paden gebruiken, dus stellen we deze variabelen in PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # geef aan waar de broncode en includes staan INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # geef aan waar de testcode staat TEST_SRC_DIRS = $(TEST_DIR) # hoe de test binary moet heten TEST_TARGET=voorbeeld # waar de cpputest bibliotheek staat CPPUTEST_HOME=/usr/local # voer MakefileWorker.mk met de hier gedefinieerde variabelen include MakefileWorker.mk

Merk op dat CPPUTEST_HOME moet worden ingesteld op de plaats waar CppUTest is geïnstalleerd. Als je een distropakket hebt geïnstalleerd, is dat meestal onder /usr/local op een Linux/mac-systeem. Als je de repo zelf hebt uitgecheckt, is dat waar die checkout ook is.

Deze opties zijn allemaal gedocumenteerd in MakefileWorker.mk.

MakefileWorker.mk voegt ook een paar makefile-doelen toe, waaronder de volgende:

  1. all - bouwt de tests die in de makefile zijn aangegeven

  2. clean - verwijdert alle object- en gcov-bestanden die voor de tests zijn gegenereerd

  3. realclean - verwijdert alle object- of gcov-bestanden in de hele mapstructuur

  4. flags - toont alle geconfigureerde flags die worden gebruikt om de tests te compileren

  5. debug - somt alle bronbestanden, objecten, afhankelijkheden en "op te ruimen" op


Code dekking

Unit testing would not be complete without a coverage report. The go-to tool for this for projects using gcc is gcov, available as part of the standard suite of gcc utilities. Cpputest integrates easily with gcov, all you need to do is add this line naar de makefile:

CPPUTEST_USE_GCOV=Y

Next, we need to make sure that the filterGcov.sh script from deze repo is in ‘/scripts/filterGcov.sh’ relative to wherever you set ‘CPPUTEST_HOME’ to be. It also needs to have execute perms.

In het voorbeeld Makefile zou het ingezet worden op '/usr/local/scripts/filterGcov.sh'. Als je CppUTest uitvoert vanuit een repo checkout, zou alles zonder wijzigingen moeten werken.


Als dat gebeurd is, kun je gewoon 'make gcov' draaien en wordt de analyse voor je gegenereerd. In ons geval moeten we 'make -B' uitvoeren om de objectbestanden te herbouwen met gcov ingeschakeld:

[]$ make -B gcov < compilation output > for d in /Users/ykuperman/code/blogpost/qa/src/code ; do \ FILES=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $FILES >> gcov_output.txt 2>>gcov_error.txt ; \ done for f in ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ done /usr/local/scripts/filterGcov.sh gcov_output.txt gcov_error.txt gcov_report.txt example.txt cat gcov_report.txt 100.00% /Users/ykuperman/code/blogpost/qa/src/code/code.cpp mkdir -p gcov mv *.gcov gcov mv gcov_* gcov See gcov directory for details

Dit voert een aantal bestanden uit naar een nieuwe "gcov"-directory. Dit zijn:

  1. code.cpp.gcov - het eigenlijke "gcov"-bestand voor de te testen code

  2. gcov_error.txt - een foutenrapport (in ons geval zou het leeg moeten zijn)

  3. gcov_output.txt - de eigenlijke uitvoer van het uitgevoerde gcov-commando

  4. gcov_report.txt - een samenvatting van de dekking voor elk getest bestand

  5. gcov_report.txt.html - een html-versie van gcov_report


Cpputest geheugenlekdetectie

Met Cpputest kun je automatisch gelekt geheugen detecteren door de standaard "malloc/free" familie van functies te herdefiniëren en in plaats daarvan zijn eigen wrappers te gebruiken. Hierdoor kunnen lekken snel worden opgespoord en gerapporteerd bij elke testuitvoering. Dit is standaard ingeschakeld in MakefileWorker.mk, dus het staat al aan met de tot nu toe beschreven stappen.

To illustrate, let’s leak some memory in test_func() !

Going back to code.c, we add a malloc()  naar de function, like so:

int test_func() { malloc(1); return 1; }

Nu, na het hercompileren, wordt de volgende fout geproduceerd:

test.cpp:9: error: Failure in TEST(AwesomeExamples, FirstExample) Memory leak(s) found. Alloc num (4) Leak size: 1 Allocated at: ./code.c and line: 6. Type: "malloc" Memory: <0x7fc9e94056d0> Content: 0000: 00 |.| Total number of leaks: 1

Dit laat zien welke test het lek veroorzaakte, waar het lek zich voordeed in de broncode, en wat er in het gelekte geheugen zat. Zeer nuttig!

Er zijn een paar bezwaren tegen deze functie:

  1. Cpputest gebruikt preprocessor macro's om dynamisch alle aanroepen naar de standaard geheugenbeheerfuncties te herdefiniëren. Dat betekent dat het alleen werkt voor aanroepen in de broncode die wordt getest, aangezien dat is wat is gecompileerd met de overrides van CppUTest. Lekken in gelinkte bibliotheken worden niet opgevangen.

  2. Soms is het de bedoeling dat geheugen dat voor de hele levensduur van het proces wordt toegewezen, niet wordt vrijgemaakt. Dit kan veel spamfouten opleveren als je een module met dit gedrag test. Om de lekdetectie uit te schakelen, kunt u het volgende doen:

CPPUTEST_USE_MEM_LEAK_DETECTION=N


Geïnteresseerd in meer?

Dit is slechts het topje van de ijsberg als het gaat om alle mogelijkheden van deze tool. Naast de hier besproken basis, heeft het ook een mocking framework, een directe C integratie laag, en een plugin framework, om een paar belangrijke te noemen. De repo bevat ook een hele map met helper scripts die kunnen helpen bij het automatiseren van enkele routinematige onderdelen van het werken met het framework.

Ik hoop dat de informatie hier u helpt de kwaliteit van uw C/C++ code te verbeteren met dit geweldige hulpmiddel!

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> aan de right time.

Your new standard in Marketing, Pay & Sales. It's Bird

The right message -> to the right person -> aan de right time.