
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
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:
all – erstellt die im Makefile angegebenen Tests
clean – entfernt alle für die Tests generierten Objekt- und gcov-Dateien
realclean – entfernt alle Objekt- oder gcov-Dateien im gesamten Verzeichnisbaum
flags – listet alle konfigurierten Flags auf, die zum Kompilieren der Tests verwendet werden
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:
code.cpp.gcov – die eigentliche ‘gcov’-Datei für den getesteten Code
gcov_error.txt – ein Fehlerbericht (in unserem Fall sollte er leer sein)
gcov_output.txt – die eigentliche Ausgabe des ausgeführten gcov-Befehls
gcov_report.txt – eine Zusammenfassung der Abdeckung für jede getestete Datei
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:
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.
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!