Komma igång med CppUTest

Komma igång med CppUTest

Komma igång med CppUTest

May 14, 2017

Publicerad av

Publicerad av

Bird

Bird

Kategori:

Kategori:

E-post

E-post

Ready to see Bird
in action?

Ready to see Bird
in action?

Getting Started with CppUTest

På SparkPost lägger vi mycket tid och kraft på att testa vår kod. Vår plattform är skriven i C, och nyligen undersökte jag möjligheten att integrera med ett enhetstestramverk som heter "CppUTest", som tillhandahåller testning i xUnit-stil för C/C++. Ramverket är robust, har många funktioner och är under aktiv utveckling, vilket gör det till ett bra val. Det tillhandahåller också ett C-integrationslager som gjorde det enkelt att använda med vår plattforms C-kod även om det mesta av ramverket är C++. Den här handledningen beskriver hur du kommer igång med CppUTest i dina egna projekt.


Ladda ner CppUTest

Den 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 hembryggd on Mac OS. Den 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 hemsida. We’re going to breeze through that and get to some of the more interesting features.


Att lägga grunden

Först och främst, låt oss skriva lite kod!

Vårt testprojekt kommer att ha en "main"-fil och kommer att innehålla ett verktygsbibliotek som heter "code". Biblioteket kommer att tillhandahålla en enkel funktion som returnerar 1 (för tillfället). Filerna kommer att läggas upp så här:

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

Låt oss börja med att skriva src/-filerna

// 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 kan vi skriva vår första testmodul:

// 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.


Makefile för projektet

Projektets makefile kommer att ligga på samma nivå som katalogerna 'src' och 't' vid roten av projektet. Den bör se ut så här:

# 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)

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 kan vi kompilera "src"-koden med makefile och se att den fungerar:

[]$ 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 world!


Test av Makefile

För testerna är det lite mer komplicerat eftersom vi måste ladda och integrera med CppUTest-biblioteket på rätt sätt.

I CppUTest-förvaret finns en fil som heter "MakefileWorker.mk". Den innehåller en hel del funktioner som gör det enkelt att bygga med CppUTest. Filen finns under "build"-katalogen i git-förvaret. För denna handledning kommer vi att anta att den har kopierats till katalogen 't/'. Den kan användas enligt följande:

# vi vill inte använda relativa sökvägar, därför sätter vi dessa variabler PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # ange var källkoden och inkluderade filer finns INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # ange var testkoden finns TEST_SRC_DIRS = $(TEST_DIR) # vad testbinärfilen skall kallas TEST_TARGET=example # var cpputest-biblioteket finns CPPUTEST_HOME=/usr/local # kör MakefileWorker.mk med de variabler som definieras här include MakefileWorker.mk

Observera att CPPUTEST_HOME måste anges till det ställe där CppUTest installerades. Om du har installerat ett distropaket kommer detta vanligtvis att vara under /usr/local på ett linux/mac-system. Om du har checkat ut repot på egen hand är det där den utcheckningen är.

Alla dessa alternativ finns dokumenterade i MakefileWorker.mk.

MakefileWorker.mk lägger också till några makefile-mål, inklusive följande:

  1. all - bygger de tester som anges av makefilen

  2. clean - tar bort alla objekt- och gcov-filer som genererats för testerna

  3. realclean - tar bort alla objekt- eller gcov-filer i hela katalogträdet

  4. flaggor - listar alla konfigurerade flaggor som används för att kompilera testerna

  5. debug - listar alla källfiler, objekt, beroenden och "saker att rensa


Täckning av kod

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 till makefile:

CPPUTEST_ANVÄND_GCOV=Y

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

I exemplet Makefile skulle den distribueras till '/usr/local/scripts/filterGcov.sh'. Om du kör CppUTest från en repokontroll bör allt fungera utan ändringar.


Med detta på plats kan du helt enkelt köra "make gcov" och analysen kommer att genereras åt dig. I vårt fall måste vi köra "make -B" för att bygga om objektfilerna med gcov aktiverat:

[]$ 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

Detta kommer att mata ut ett antal filer till en ny 'gcov'-katalog. Dessa är

  1. code.cpp.gcov - den faktiska "gcov"-filen för den kod som testas

  2. gcov_error.txt - en felrapport (i vårt fall ska den vara tom)

  3. gcov_output.txt - den faktiska utdata från gcov-kommandot som kördes

  4. gcov_report.txt - en sammanfattning av täckningen för varje fil som testas

  5. gcov_report.txt.html - en html-version av gcov_report


Cpputest Detektering av minnesläckage

Med Cpputest kan du automatiskt upptäcka minnesläckor genom att omdefiniera standardfunktionerna "malloc/free" så att de istället använder sina egna wrappers. Detta gör det möjligt att snabbt fånga upp läckor och rapportera dem för varje testkörning. Detta är aktiverat som standard i MakefileWorker.mk, så det är redan på med de steg som beskrivits hittills.

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

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

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

Nu, efter omkompilering, produceras följande fel:

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

Detta visar vilket test som orsakade läckan, var läckan skedde i källkoden och vad som fanns i det läckta minnet. Mycket användbart!

Det finns ett par invändningar mot denna funktion:

  1. Cpputest använder preprocesser-makron för att dynamiskt omdefiniera alla anrop till standardfunktioner för minneshantering. Det betyder att det bara fungerar för anrop i källkoden som testas eftersom det är vad som kompileras in med CppUTests överskrivningar. Läckor i länkade bibliotek kommer inte att fångas upp.

  2. Ibland är det inte meningen att minne som allokerats för hela processens livslängd ska frigöras. Detta kan leda till en hel del skräpfel om du testar en modul med detta beteende. Om du vill inaktivera läckagedetekteringen kan du göra så här:

CPPUTEST_ANVÄND_MEM_LEAK_DETECTION=N


Intresserad av mer?

Detta är bara toppen av isberget när det gäller alla funktioner som finns i det här verktyget. Förutom de grunder som diskuteras här har det också ett mocking-ramverk, ett direkt C-integrationslager och ett plugin-ramverk, för att nämna några viktiga. Repot innehåller också en hel katalog med hjälpskript som kan hjälpa till att automatisera några av de rutinmässiga delarna av att arbeta med ramverket.

Jag hoppas att informationen här hjälper dig att förbättra kvaliteten på din C/C++-kod med detta fantastiska verktyg!

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

The right message -> to the right person -> vid right time.

By clicking "See Bird" you agree to Bird's Meddelande om integritet.

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

The right message -> to the right person -> vid right time.

By clicking "See Bird" you agree to Bird's Meddelande om integritet.