Bereik

Grow

Manage

Automate

Bereik

Grow

Manage

Automate

Aan de slag met CppUTest

Bird

14 mei 2017

E-mail

1 min read

Aan de slag met CppUTest

Bird

14 mei 2017

E-mail

1 min read

Aan de slag met CppUTest

Bij SparkPost besteden we veel tijd en moeite aan het testen van onze code. Ons platform is geschreven in C, en onlangs heb ik onderzoek gedaan naar integratie met een unit testing framework genaamd "CppUTest", dat xUnit-stijl tests biedt voor C/C++. Dit framework is robuust, rijk aan functies en in actieve ontwikkeling, wat het een uitstekende keuze maakt. Het biedt ook een C-integratielaag, waardoor het gemakkelijk te gebruiken was met onze platform C-code, zelfs al is het grootste deel van het framework C++. Deze tutorial behandelt hoe je aan de slag kunt gaan met CppUTest in je eigen projecten.

Bij SparkPost steken we veel tijd en moeite in het testen van onze code. Ons platform is geschreven in C en recentelijk heb ik onderzoek gedaan naar de integratie met een unit testing framework genaamd “CppUTest”, dat xUnit-stijl testen biedt voor C/C++. Dit framework is robuust, rijk aan functies en in actieve ontwikkeling, wat het een geweldige keuze maakt. Het biedt ook een C-integratielaag, waardoor het gemakkelijk te gebruiken is met onze platform C-code, zelfs als het grootste deel van het framework in C++ is. Deze tutorial behandelt hoe u kunt beginnen met CppUTest voor uw eigen projecten.

CppUTest downloaden

De CppUTest projectpagina is beschikbaar hier, en de repository staat op github. Het is ook opgenomen in de pakketbeheer repositories voor vele linux distributies, evenals homebrew op Mac OS. De voorbeelden die volgen zijn uitgevoerd op Mac OS X, maar ze zijn afgeleid van code geschreven voor Red Hat, het besturingssysteem waarop ons platform draait.

De basisprincipes zijn goed gedocumenteerd op de homepage van CppUTest. We gaan daar snel doorheen en naar enkele van de interessantere functies.

Het Fundament Leggen

Eerst en vooral, laten we wat code schrijven!

Ons testproject zal een 'main'-bestand hebben en een hulpbibliotheek genaamd 'code' bevatten. De bibliotheek zal een eenvoudige functie bieden die 1 retourneert (voor nu). De bestanden zijn als volgt ingedeeld:

├── src │ ├── code │ │ ├── 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

Nu, laten we de tests doen, die in de t/ directory zullen staan.  Het eerste wat je moet doen is een test runner opzetten die onze testbestanden zal uitvoeren. Dit is ook de 'main' functie die zal uitvoeren zodra dit allemaal is gecompileerd:

// 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); }

Vervolgens moeten we makefiles schrijven.  We hebben er twee nodig: één voor de projectbestanden onder src/, en één voor de tests.

Eerst en vooral, laten we wat code schrijven!

Ons testproject zal een 'main'-bestand hebben en een hulpbibliotheek genaamd 'code' bevatten. De bibliotheek zal een eenvoudige functie bieden die 1 retourneert (voor nu). De bestanden zijn als volgt ingedeeld:

├── src │ ├── code │ │ ├── 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

Nu, laten we de tests doen, die in de t/ directory zullen staan.  Het eerste wat je moet doen is een test runner opzetten die onze testbestanden zal uitvoeren. Dit is ook de 'main' functie die zal uitvoeren zodra dit allemaal is gecompileerd:

// 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); }

Vervolgens moeten we makefiles schrijven.  We hebben er twee nodig: één voor de projectbestanden onder src/, en één voor de tests.

Eerst en vooral, laten we wat code schrijven!

Ons testproject zal een 'main'-bestand hebben en een hulpbibliotheek genaamd 'code' bevatten. De bibliotheek zal een eenvoudige functie bieden die 1 retourneert (voor nu). De bestanden zijn als volgt ingedeeld:

├── src │ ├── code │ │ ├── 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

Nu, laten we de tests doen, die in de t/ directory zullen staan.  Het eerste wat je moet doen is een test runner opzetten die onze testbestanden zal uitvoeren. Dit is ook de 'main' functie die zal uitvoeren zodra dit allemaal is gecompileerd:

// 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); }

Vervolgens moeten we makefiles schrijven.  We hebben er twee nodig: één voor de projectbestanden onder src/, en één voor de tests.

Project Makefile

Het project makefile zal op hetzelfde niveau staan als de ‘src’ en ‘t’ directories in de root van het project. Het zou er als volgt uit moeten zien:

# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=example 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)

Merk op dat dit ‘make -C’ gebruikt voor de testdoelen – wat betekent dat het ‘make’ opnieuw zal aanroepen met behulp van het makefile in de testdirectory.

Op dit punt kunnen we de ‘src’ code compileren met het 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 example []$ ./example hello wereld!

Tests Makefile

Voor de tests zijn de zaken iets complexer omdat we correct moeten laden en integreren met de CppUTest-bibliotheek.

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

# we willen geen relatieve paden gebruiken, dus we stellen deze variabelen in PROJECT_DIR=/pad/naar/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # geef aan waar de broncode en includes zich bevinden INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # geef aan waar de testcode zich bevindt TEST_SRC_DIRS = $(TEST_DIR) # hoe de testbinary te noemen TEST_TARGET=example # waar de cpputest-bibliotheek zich bevindt CPPUTEST_HOME=/usr/local # voer MakefileWorker.mk uit met de hier gedefinieerde variabelen include MakefileWorker.mk

Merk op dat CPPUTEST_HOME moet worden ingesteld op waar CppUTest is geïnstalleerd. Als je een distro-pakket hebt geïnstalleerd, bevindt dit zich meestal onder /usr/local op een linux/mac-systeem. Als je de repo zelf hebt uitgecheckt, is het waar die checkout is.

Deze opties worden allemaal gedocumenteerd in MakefileWorker.mk.

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

  1. alle – bouwt de tests die door de makefile worden aangegeven

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

  3. echtschoon – verwijdert alle object- of gcov-bestanden in de gehele directorystructuur

  4. vlaggen – vermeldt alle geconfigureerde vlaggen die gebruikt worden om de tests te compileren

  5. debug – vermeldt alle bronbestanden, objecten, afhankelijkheden en 'te cleanen zaken'

Code Dekking

Unit testen zou niet compleet zijn zonder een dekkingsrapport. Het favoriete hulpmiddel hiervoor voor projecten die gcc gebruiken is gcov, beschikbaar als onderdeel van de standaard suite van gcc-hulpprogramma's. Cpputest integreert gemakkelijk met gcov, alles wat je hoeft te doen is deze regel toevoegen aan het makefile:

CPPUTEST_USE_GCOV=Y

Vervolgens moeten we ervoor zorgen dat het filterGcov.sh script van deze repo in ‘/scripts/filterGcov.sh’ staat, relatief ten opzichte van waar je ‘CPPUTEST_HOME’ hebt ingesteld. Het heeft ook uitvoorrechten nodig.

In het voorbeeld van het Makefile zou het worden geplaatst in ‘/usr/local/scripts/filterGcov.sh’. Als je CppUTest vanuit een repo checkout uitvoert, zou alles zonder aanpassing moeten werken.




Met dat alles op zijn plaats, kun je eenvoudig ‘make gcov’ uitvoeren en de analyse zal voor je worden gegenereerd. In ons geval moeten we ‘make -B’ uitvoeren om de objectbestanden met gcov ingeschakeld opnieuw op te bouwen:

[]$ make -B gcov < compilatie uitvoer > voor d in /Users/ykuperman/code/blogpost/qa/src/code ; doe \ BESTANDEN=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $BESTANDEN >> gcov_output.txt 2>>gcov_error.txt ; \ gedaan voor f in ; doe \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ gedaan /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 Zie gcov directory voor details

Dit genereert een aantal bestanden naar een nieuwe ‘gcov’ directory. Deze zijn:

  1. code.cpp.gcov – het daadwerkelijke ‘gcov’ bestand voor de code die wordt getest

  2. gcov_error.txt – een foutrapport (in ons geval zou dit leeg moeten zijn)

  3. gcov_output.txt – de daadwerkelijke uitvoer van het uitgevoerde gcov commando

  4. gcov_report.txt – een samenvatting van de dekking voor elk bestand dat wordt getest

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

Cpputest Geheugenlekdetectie

Cpputest stelt je in staat om automatisch gelekt geheugen te detecteren door de standaard “malloc/free” functies te herdefiniëren en in plaats daarvan de eigen wrappers te gebruiken. Hierdoor kan het snel lekken opsporen en rapporteren voor elke testuitvoering. Dit is standaard ingeschakeld in MakefileWorker.mk, dus het is al aan met de tot nu toe beschreven stappen.

Ter illustratie, laten we wat geheugen lekken in test_func() !

Teruggaand naar code.c, voegen we een malloc() toe aan de functie, zoals hieronder:

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

Nu, na hercompilatie, 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 en regel: 6. Type: "malloc" Memory: <0x7fc9e94056d0> Content: 0000: 00 |.| Totale aantal lekken: 1

Dit laat zien welke test het lek veroorzaakte, waar het lek gebeurde in de broncode, en wat er in het gelekte geheugen zat. Erg handig!

Er zijn een paar kanttekeningen bij deze functie:

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

  2. Soms is geheugen dat voor de gehele looptijd van het proces is toegewezen niet bedoeld om vrijgemaakt te worden. Dit kan een hoop spamachtige fouten veroorzaken als je een module test met dit gedrag. Om de lekdetectie uit te schakelen, kun je dit doen:

CPPUTEST_USE_MEM_LEAK_DETECTION=N

Geïnteresseerd in meer?

Dit is slechts het topje van de ijsberg als het gaat om alle functies die in deze tool zijn opgenomen. Naast de basis die hier wordt besproken, heeft het ook een mocking-framework, een directe C-integratielaag 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 van de routinematige onderdelen van het werken met het framework.

Ik hoop dat de informatie hier je helpt de kwaliteit van je C/C++-code te verbeteren met deze geweldige tool!

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.

Laten we je in contact brengen met een Bird-expert.
Bekijk de volledige kracht van de Bird in 30 minuten.

Door te verzenden, ga je ermee akkoord dat Bird contact met je mag opnemen over onze producten en diensten.

U kunt zich op elk moment afmelden. Zie Bird's Privacyverklaring voor details over gegevensverwerking.

R

Bereik

G

Grow

M

Manage

A

Automate

Nieuwsbrief

Blijf op de hoogte met Bird via wekelijkse updates in je inbox.