Erreichen

Grow

Manage

Automate

Erreichen

Grow

Manage

Automate

Erste Schritte mit CppUTest

Vogel

14.05.2017

E-Mail

1 min read

Erste Schritte mit CppUTest

Vogel

14.05.2017

E-Mail

1 min read

Erste Schritte mit CppUTest

Bei SparkPost investieren wir viel Zeit und Mühe in das Testen unseres Codes. Unsere Plattform ist in C geschrieben, und kürzlich habe ich recherchiert, wie man ein Unit-Test-Framework namens „CppUTest“ integriert, das xUnit-ähnliches Testen für C/C++ bietet. Dieses Framework ist robust, funktionsreich und wird aktiv weiterentwickelt, was es zu einer ausgezeichneten Wahl macht. Es bietet auch eine C-Integrationsschicht, die es einfach machte, es mit unserem Plattform-C-Code zu verwenden, obwohl der Großteil des Frameworks in C++ geschrieben ist. Dieses Tutorial behandelt, wie man mit CppUTest in eigenen Projekten beginnt.

Bei SparkPost investieren wir viel Zeit und Mühe in das Testen unseres Codes. Unsere Plattform ist in C geschrieben, und kürzlich habe ich die Integration mit einem Unit-Test-Framework namens „CppUTest“ recherchiert, das xUnit-ähnliches Testen für C/C++ bietet. Dieses Framework ist robust, funktionsreich und wird aktiv weiterentwickelt, was es zu einer großartigen Wahl macht. Es bietet auch eine C-Integrationsschicht, die die Verwendung mit unserem Plattform-C-Code erleichterte, obwohl der Großteil des Frameworks in C++ geschrieben ist. Dieses Tutorial erklärt, wie Sie mit CppUTest in Ihren eigenen Projekten beginnen können.

CppUTest herunterladen

Die CppUTest-Projektseite ist hier verfügbar, und das Repository befindet sich auf github. Es ist auch in den Paketverwaltungsrepositorien vieler Linux-Distros sowie im homebrew auf Mac OS enthalten. Die folgenden Beispiele wurden auf Mac OS X ausgeführt, stammen jedoch aus Code, der für Red Hat geschrieben wurde, das Betriebssystem, auf dem unsere Plattform läuft.

Die Grundlagen sind auf der Homepage von CppUTest gut dokumentiert. Wir werden das nur kurz überfliegen und uns einigen der interessanteren Funktionen zuwenden.

Das Fundament legen

Erst mal, lassen Sie uns etwas Code schreiben!

Unser Testprojekt wird eine ‘main’ Datei haben und wird eine Bibliothek namens ‘code’ einbinden. Die Bibliothek bietet eine einfache Funktion, die 1 zurückgibt (vorerst). Die Dateien werden folgendermaßen angeordnet:

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

Fangen wir an, die src/ Dateien zu schreiben

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

Nun, lassen Sie uns die Tests machen, die im t/ Verzeichnis liegen werden.  Das Erste, was zu tun ist, ist einen Testrunner einzurichten, der unsere Testdateien ausführen wird. Dies ist auch die ‘main’  Funktion, die ausgeführt wird, sobald alles kompiliert ist:

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

Als nächstes müssen wir Makefiles schreiben.  Wir brauchen zwei: eines für die Projektdateien unter src/, und eines für die Tests.

Erst mal, lassen Sie uns etwas Code schreiben!

Unser Testprojekt wird eine ‘main’ Datei haben und wird eine Bibliothek namens ‘code’ einbinden. Die Bibliothek bietet eine einfache Funktion, die 1 zurückgibt (vorerst). Die Dateien werden folgendermaßen angeordnet:

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

Fangen wir an, die src/ Dateien zu schreiben

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

Nun, lassen Sie uns die Tests machen, die im t/ Verzeichnis liegen werden.  Das Erste, was zu tun ist, ist einen Testrunner einzurichten, der unsere Testdateien ausführen wird. Dies ist auch die ‘main’  Funktion, die ausgeführt wird, sobald alles kompiliert ist:

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

Als nächstes müssen wir Makefiles schreiben.  Wir brauchen zwei: eines für die Projektdateien unter src/, und eines für die Tests.

Erst mal, lassen Sie uns etwas Code schreiben!

Unser Testprojekt wird eine ‘main’ Datei haben und wird eine Bibliothek namens ‘code’ einbinden. Die Bibliothek bietet eine einfache Funktion, die 1 zurückgibt (vorerst). Die Dateien werden folgendermaßen angeordnet:

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

Fangen wir an, die src/ Dateien zu schreiben

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

Nun, lassen Sie uns die Tests machen, die im t/ Verzeichnis liegen werden.  Das Erste, was zu tun ist, ist einen Testrunner einzurichten, der unsere Testdateien ausführen wird. Dies ist auch die ‘main’  Funktion, die ausgeführt wird, sobald alles kompiliert ist:

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

Als nächstes müssen wir Makefiles schreiben.  Wir brauchen zwei: eines für die Projektdateien unter src/, und eines für die Tests.

Projekt Makefile

Die Projekt-Makefile wird auf derselben Ebene wie die 'src'- und 't'-Verzeichnisse im Stammverzeichnis des Projekts liegen. Es sollte so aussehen:

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

Beachten Sie, dass hierfür ‘make -C’  für die Test-Ziele verwendet wird – was bedeutet, dass es ‘make’  erneut verwendet, indem das Makefile im Testverzeichnis aufgerufen wird.

An diesem Punkt können wir den ‘src’-Code mit dem Makefile kompilieren und sehen, dass es 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 hallo welt!

Tests Makefile

Für die Tests ist es etwas komplizierter, da wir die CppUTest-Bibliothek korrekt laden und integrieren müssen.

Das CppUTest-Repository stellt eine Datei namens „MakefileWorker.mk“ bereit. Sie bietet viele Funktionen, die das Erstellen mit CppUTest einfach machen. Die Datei befindet sich im Verzeichnis „build“ im git-Repository. Für dieses Tutorial nehmen wir an, dass sie in das Verzeichnis ‘t/’ kopiert wurde. Sie kann wie folgt verwendet werden:

# Wir möchten keine relativen Pfade verwenden, daher setzen wir diese Variablen PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # Geben Sie an, wo sich der Quellcode und die Includes befinden INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # Geben Sie an, wo sich der Testcode befindet TEST_SRC_DIRS = $(TEST_DIR) # Wie der Test-Binary genannt werden soll TEST_TARGET=example # Wo die cpputest-Bibliothek lokalisiert ist CPPUTEST_HOME=/usr/local # Führen Sie MakefileWorker.mk mit den hier definierten Variablen aus 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, befindet sich dies typischerweise unter /usr/local auf einem Linux/Mac-System. Wenn Sie das Repo selbst ausgecheckt haben, dann ist es an dem Ort, wo dieses Checkout ist.

Diese Optionen sind alle in MakefileWorker.mk dokumentiert.

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

  1. all – erstellt die im Makefile angegebenen Tests

  2. clean – entfernt alle für die Tests generierten Objekt- und gcov-Dateien

  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 Quellcodedateien, Objekte, Abhängigkeiten und ‚zu reinigende Elemente‘ auf

Code Coverage

Unit Tests wären ohne einen Abdeckungsbericht nicht vollständig. Das Standardwerkzeug dafür bei Projekten, die gcc verwenden, ist gcov, das als Teil der Standard-Suite von gcc-Dienstprogrammen verfügbar ist. Cpputest integriert sich leicht mit gcov, alles was Sie tun müssen, ist diese Zeile zur Makefile hinzuzufügen:

CPPUTEST_USE_GCOV=Y

Als nächstes müssen wir sicherstellen, dass das Script filterGcov.sh aus diesem Repo unter '/scripts/filterGcov.sh' relativ zu dem Verzeichnis, in dem Sie 'CPPUTEST_HOME' gesetzt haben, vorhanden ist. Es benötigt auch Ausführungsberechtigungen.

Im Beispiel-Makefile würde es unter ‘/usr/local/scripts/filterGcov.sh’ bereitgestellt. Wenn Sie CppUTest von einem Repo-Checkout ausführen, sollte alles ohne Modifikation funktionieren.




Damit können Sie einfach ‘make gcov’ ausführen und die Analyse wird für Sie generiert. In unserem Fall müssen wir ‘make -B’ ausführen, um die Objektdateien mit aktivierten gcov neu zu erstellen:

[]$ make -B gcov < Compilation-Ausgabe > für d in /Users/ykuperman/code/blogpost/qa/src/code ; do \ DATEIEN=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $DATEIEN >> gcov_output.txt 2>>gcov_error.txt ; \ getan für f in ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ getan /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 Siehe gcov-Verzeichnis für Details

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

  1. code.cpp.gcov – die eigentliche ‘gcov’-Datei für den getesteten Code

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

  3. gcov_output.txt – die eigentliche Ausgabe des ausgeführten gcov-Befehls

  4. gcov_report.txt – eine Zusammenfassung der Abdeckung für jede getestete Datei

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

Cpputest Memory Leak Detection

Cpputest ermöglicht es Ihnen, Lecks im Speicher automatisch zu erkennen, indem die Standardfunktionen „malloc/free“ durch eigene Wrapper ersetzt werden. Dadurch können undichte Stellen schnell entdeckt und für jede Testausführung gemeldet werden. Diese Funktion ist in MakefileWorker.mk standardmäßig aktiviert, sodass sie mit den bisher beschriebenen Schritten bereits eingeschaltet ist.

Um dies zu veranschaulichen, lassen Sie uns etwas Speicher in test_func() lecken !

Gehen wir zurück zu code.c, fügen wir der Funktion ein malloc() hinzu, so:

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

Nach der erneuten Kompilierung wird nun der folgende Fehler erzeugt:

test.cpp:9: Fehler: Fehler in TEST(AwesomeExamples, FirstExample) Speicherleck(s) gefunden. Alloc num (4) Leakgröße: 1 Allokiert bei: ./code.c und Zeile: 6. Typ: „malloc“ Speicher: <0x7fc9e94056d0> Inhalt: 0000: 00 |.| Gesamtzahl der Lecks: 1

Dies zeigt, welcher Test das Leck verursacht hat, wo das Leck im Quellcode aufgetreten ist und was im undichten Speicher enthalten war. Sehr hilfreich!

Es gibt ein paar Einschränkungen bei dieser Funktion:

  1. Cpputest verwendet Präprozessormakros, um alle Aufrufe der Standard-Speicherverwaltungsfunktionen dynamisch neu zu definieren. Das bedeutet, dass es nur für Aufrufe im zu testenden Quellcode funktioniert, da dies mit CppUTest-Überschreibungen kompiliert wird. Lecks in verlinkten Bibliotheken werden nicht erfasst.

  2. Manchmal ist der für die gesamte Lebensdauer des Prozesses zugewiesene Speicher nicht dafür gedacht, freigegeben zu werden. Dies kann viele lästige Fehler verursachen, wenn Sie ein Modul mit diesem Verhalten testen. Um die Leckerkennung zu deaktivieren, können Sie Folgendes tun:

CPPUTEST_USE_MEM_LEAK_DETECTION=N

Interessiert an mehr?

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

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

Lassen Sie uns Sie mit einem Bird-Experten verbinden.
Erleben Sie die volle Macht des Bird in 30 Minuten.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Unternehmen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Lassen Sie uns Sie mit einem Bird-Experten verbinden.
Erleben Sie die volle Macht des Bird in 30 Minuten.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Unternehmen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Lassen Sie uns Sie mit einem Bird-Experten verbinden.
Erleben Sie die volle Macht des Bird in 30 Minuten.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

R

Erreichen

G

Grow

M

Manage

A

Automate

Unternehmen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.