Erste Schritte mit CppUTest

Erste Schritte mit CppUTest

Erste Schritte mit CppUTest

May 14, 2017

Herausgegeben von

Herausgegeben von

Bird

Bird

-

Kategorie:

Kategorie:

E-Mail

E-Mail

Ready to see Bird
in action?

Ready to see Bird
in action?

Getting Started with CppUTest

Bei SparkPost stecken wir viel Zeit und Mühe in das Testen unseres Codes. Unsere Plattform ist in C geschrieben, und vor kurzem habe ich mich mit der Integration eines Unit-Testing-Frameworks namens "CppUTest" beschäftigt, das xUnit-ähnliche Tests für C/C++ bietet. Dieses Framework ist robust, funktionsreich und wird aktiv weiterentwickelt, was es zu einer guten Wahl macht. Es bietet auch eine C-Integrationsschicht, die die Verwendung mit unserem Plattform-C-Code vereinfacht, obwohl der größte Teil des Frameworks in C++ geschrieben ist. In diesem Tutorial erfahren Sie, wie Sie CppUTest für Ihre eigenen Projekte einsetzen können.


Herunterladen von CppUTest

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


Legen des Fundaments

Das Wichtigste zuerst: Schreiben wir etwas Code!

Unser Testprojekt wird eine "main"-Datei haben und eine Hilfsbibliothek namens "code" enthalten. Die Bibliothek wird eine einfache Funktion bereitstellen, die (vorerst) 1 zurückgibt. Die Dateien werden wie folgt aufgebaut sein:

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

Beginnen wir mit dem Schreiben der src/-Dateien

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

Jetzt können wir unser erstes Testmodul schreiben:

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


Projekt Makefile

Das Makefile des Projekts befindet sich auf der gleichen Ebene wie die Verzeichnisse "src" und "t" im Stammverzeichnis des Projekts. Es sollte wie folgt aussehen:

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

Jetzt können wir den 'src'-Code mit dem Makefile kompilieren und sehen, dass er funktioniert:

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


Tests Makefile

Für die Tests sind die Dinge ein wenig komplizierter, da wir die CppUTest-Bibliothek ordnungsgemäß laden und in sie integrieren müssen.

Das CppUTest-Repository bietet eine Datei namens "MakefileWorker.mk". Sie bietet eine Vielzahl von Funktionen, die das Bauen mit CppUTest vereinfachen. Die Datei befindet sich im Verzeichnis "build" des Git-Repositorys. Für dieses Tutorial gehen wir davon aus, dass sie in das Verzeichnis "t/" kopiert wurde. Sie kann wie folgt verwendet werden:

# wir wollen keine relativen Pfade verwenden, also setzen wir diese Variablen PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # geben an, wo sich der Quellcode und die Includes befinden INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # angeben, wo sich der Testcode befindet TEST_SRC_DIRS = $(TEST_DIR) # wie das Test-Binary genannt werden soll TEST_TARGET=example # wo sich die cpputest-Bibliothek befindet CPPUTEST_HOME=/usr/local # MakefileWorker ausführen.mk mit den hier definierten Variablen include MakefileWorker.mk

Beachten Sie, dass CPPUTEST_HOME auf den Ort gesetzt werden muss, an dem CppUTest installiert wurde. Wenn Sie ein Distro-Paket installiert haben, wird dies normalerweise unter /usr/local auf einem Linux/Mac-System sein. Wenn Sie das Projektarchiv selbst ausgecheckt haben, ist dies der Ort, an dem Sie es ausgecheckt haben.

Diese Optionen sind alle in MakefileWorker.mk dokumentiert.

MakefileWorker.mk fügt auch ein paar Makefile-Ziele hinzu, darunter die folgenden:

  1. all - erstellt die im Makefile angegebenen Tests

  2. clean - entfernt alle Objekt- und gcov-Dateien, die für die Tests erzeugt wurden

  3. realclean - Entfernt alle Objekt- oder gcov-Dateien im gesamten Verzeichnisbaum

  4. flags - listet alle konfigurierten Flags auf, die zum Kompilieren der Tests verwendet werden

  5. debug - listet alle Quelldateien, Objekte, Abhängigkeiten und "zu bereinigendes Zeug" auf


Code-Abdeckung

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

CPPUTEST_USE_GCOV=Y

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

Im Beispiel-Makefile würde es in "/usr/local/scripts/filterGcov.sh" bereitgestellt werden. Wenn Sie CppUTest aus einem Repo-Checkout ausführen, sollte alles ohne Änderungen funktionieren.


Danach können Sie einfach "make gcov" ausführen und die Analyse wird für Sie erstellt. In unserem Fall müssen wir "make -B" ausführen, um die Objektdateien mit aktiviertem gcov neu zu erstellen:

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

Dadurch wird eine Reihe von Dateien in ein neues 'gcov'-Verzeichnis ausgegeben. Diese sind:

  1. code.cpp.gcov - die eigentliche 'gcov'-Datei für den zu prüfenden Code

  2. gcov_error.txt - ein Fehlerbericht (in unserem Fall sollte er leer sein)

  3. gcov_output.txt - die tatsächliche Ausgabe des ausgeführten gcov-Befehls

  4. gcov_report.txt - eine Zusammenfassung der Abdeckung für jede zu testende Datei

  5. gcov_report.txt.html - eine html-Version von gcov_report


Cpputest Speicherleck-Erkennung

Cpputest ermöglicht es Ihnen, Speicherlecks automatisch zu erkennen, indem es die Standardfunktionen "malloc/free" umdefiniert und stattdessen seine eigenen Wrapper verwendet. Dies ermöglicht es, Lecks schnell zu erkennen und sie bei jeder Testausführung zu melden. Diese Funktion ist standardmäßig in MakefileWorker.mk aktiviert, so dass sie bereits mit den bisher beschriebenen Schritten aktiviert ist.

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

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

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

Jetzt, nach der Neukompilierung, tritt folgender Fehler auf

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

Daraus geht hervor, welcher Test das Leck verursacht hat, wo das Leck im Quellcode auftrat und was sich im ausgelaufenen Speicher befand. Sehr hilfreich!

Bei dieser Funktion gibt es einige Vorbehalte:

  1. Cpputest verwendet Präprozessormakros, um alle Aufrufe der Standard-Speicherverwaltungsfunktionen dynamisch umzudefinieren. Das bedeutet, dass es nur bei Aufrufen im zu testenden Quellcode funktioniert, da dieser mit den Überschreibungen von CppUTest einkompiliert wurde. Lecks in verlinkten Bibliotheken werden nicht erkannt.

  2. Manchmal ist der Speicher, der für die gesamte Lebensdauer des Prozesses zugewiesen wird, nicht dafür gedacht, freigegeben zu werden. Dies kann eine Menge spammiger Fehler verursachen, wenn Sie ein Modul mit diesem Verhalten testen. Um die Leckerkennung zu deaktivieren, können Sie dies tun:

CPPUTEST_USE_MEM_LEAK_DETECTION=N


Interessiert an mehr?

Dies ist nur die Spitze des Eisbergs, wenn es um die in diesem Tool enthaltenen Funktionen geht. Neben den hier besprochenen Grundlagen verfügt es auch über ein Mocking-Framework, eine direkte C-Integrationsschicht und ein Plugin-Framework, um nur einige der wichtigsten zu nennen. Das Repo enthält auch ein ganzes Verzeichnis von Hilfsskripten, die dabei helfen können, einige der Routineaufgaben bei der Arbeit mit dem Framework zu automatisieren.

Ich hoffe, die Informationen hier helfen Ihnen, die Qualität Ihres C/C++-Codes mit diesem großartigen Werkzeug zu verbessern!

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

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

By clicking "See Bird" you agree to Bird's Hinweis zum Datenschutz.

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

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

By clicking "See Bird" you agree to Bird's Hinweis zum Datenschutz.