Programming lesson
Unit-Testing in C++ mit GoogleTest: Von White-Box zu Black-Box-Tests – Ein praktischer Leitfaden für COP 4600
Lerne in diesem Tutorial, wie du mit GoogleTest Unit-Tests in C++ schreibst – von der Installation über White-Box-Tests bis zu Black-Box-Tests für ein fiktives Buchungssystem. Ideal für Studierende der Betriebssysteme (COP 4600).
Unit-Testing in C++ mit GoogleTest: Von White-Box zu Black-Box-Tests
Unit-Tests sind das Fundament robuster Softwareentwicklung. Sie helfen dir, Fehler früh zu erkennen und sicherzustellen, dass dein Code auch nach Änderungen korrekt bleibt. In diesem Tutorial erfährst du, wie du mit dem GoogleTest-Framework Unit-Tests in C++ schreibst – von der Installation über White-Box-Tests bis zu Black-Box-Tests. Die Beispiele sind an die Aufgabenstellung von COP 4600 Ex4: Unit Testing angelehnt, ohne die Lösung vorwegzunehmen.
Warum Unit-Testing? Ein Trend-Vergleich
Stell dir vor, du entwickelst die KI für ein autonomes Fahrsystem wie Teslas FSD (Full Self-Driving). Jede Änderung an der Software könnte fatale Folgen haben. Unit-Tests sind wie die Sicherheitschecks vor jeder Fahrt: Sie prüfen, ob jede Komponente – vom Spurhalteassistenten bis zur Notbremsung – einzeln korrekt funktioniert. Ohne diese Tests wäre das Risiko katastrophal. Genauso verhält es sich in der Systemprogrammierung: Unit-Tests geben dir das Vertrauen, dass dein Code unter allen Bedingungen stabil läuft.
Teil 1: Einführung in GoogleTest
Installation von GoogleTest
Bevor du mit dem Schreiben von Tests beginnst, musst du GoogleTest installieren. Die folgende Schritt-für-Schritt-Anleitung zeigt dir, wie du das Framework unter Linux (Ubuntu) einrichtest:
sudo apt-get install cmake
git clone https://github.com/google/googletest.git -b v1.13.0
cd googletest
mkdir build
cd build
cmake .. -DBUILD_GMOCK=OFF
make
sudo make installNach der Installation kannst du GoogleTest in deinen Projekten verwenden. Der Befehl cmake .. -DBUILD_GMOCK=OFF erzeugt ein Makefile, das nur GoogleTest (ohne GoogleMock) baut. Das spart Zeit und Speicherplatz.
Aufbau eines Unit-Tests
Ein Unit-Test in GoogleTest besteht aus einem Testfall, der mit dem Makro TEST() definiert wird. Der Test enthält eine oder mehrere Assertions, die das erwartete Verhalten prüfen. Hier ein einfaches Beispiel für eine Fakultätsfunktion:
#include "gtest/gtest.h"
#include "Factorial.h"
TEST(FactorialTest, HandlesZeroInput) {
int result = Factorial(0);
ASSERT_EQ(result, 1);
}Das Test-Suite heißt FactorialTest, der Testname HandlesZeroInput. Die Assertion ASSERT_EQ prüft, ob result gleich 1 ist. Schlägt die Assertion fehl, wird der Test als fehlgeschlagen markiert.
Teil 2: White-Box-Testing
Beim White-Box-Testing kennst du die Implementierung der zu testenden Funktion. Du kannst Tests schreiben, die gezielt Schwachstellen ausnutzen oder Grenzfälle abdecken. Betrachten wir die Funktion isDivisible:
bool isDivisible(int a, int b) {
return (b % a == 0);
}Eine vollständige Testabdeckung würde 18 Tests erfordern, aber für diese Übung reichen 4. Hier ein Beispiel für einen Testfall:
TEST(DivisibilityTest, PositiveDividesPositive) {
EXPECT_TRUE(isDivisible(2, 4));
}Weitere Tests könnten sein: a positiv, b positiv, aber a teilt b nicht; a positiv, b = 0; a negativ, b positiv, und a teilt b. Denk daran, dass b % a bei a = 0 zu einem Laufzeitfehler führt – ein klassischer Fall, den du testen solltest.
Teil 3: Black-Box-Testing
Beim Black-Box-Testing kennst du die Implementierung nicht. Du schreibst Tests allein auf Basis der Spezifikation. Das ist besonders wichtig, wenn du Komponenten testest, die von anderen Teams entwickelt wurden. In der Übung geht es um ein Buchungssystem für Flüge. Du testest die Schnittstellen aus BackEnd.h.
Beispiel: Test für eine Abfrage
Die Spezifikation besagt: Eine Abfrage liefert alle verfügbaren Buchungen von einem Quell- zu einem Zielflughafen, aber keine Buchungen mit vollen Flügen. Ein Test könnte so aussehen:
TEST(QueryTest, ReturnsDirectFlights) {
// Setup: Flughäfen und Flüge anlegen
Airport src("JFK");
Airport dst("LAX");
Flight flight(src, dst, 10, 5); // 5 freie Plätze
// Führe Abfrage durch
std::vector<Booking> results = query(src, dst);
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0].getFlights().size(), 1);
}Ein weiterer Test prüft, dass bei vollem Flug keine Buchung zurückgegeben wird:
TEST(QueryTest, ExcludesFullFlights) {
Airport src("MIA");
Airport dst("ORD");
Flight flight(src, dst, 14, 0); // 0 Plätze = voll
std::vector<Booking> results = query(src, dst);
EXPECT_TRUE(results.empty());
}Besonderheiten bei Verbindungen
Eine Buchung kann entweder einen Direktflug oder zwei Flüge mit Umstieg enthalten. Der zweite Flug muss mindestens 2 Stunden nach dem ersten abfliegen. Teste auch, dass bei existierendem Direktflug keine Verbindungen zurückgegeben werden:
TEST(QueryTest, PrefersDirectFlights) {
// Setup mit Direktflug und Verbindungsflügen
// Erwarte nur den Direktflug in den Ergebnissen
}Best Practices und häufige Fehler
- Teste Grenzfälle: Nullwerte, leere Listen, maximale/minimale Integerwerte.
- Verwende aussagekräftige Testnamen:
ReturnsTrueWhenDivisibleist besser alsTest1. - Halte Tests unabhängig: Jeder Test sollte isoliert laufen können.
- Nutze
EXPECT_*stattASSERT_*, wenn du auch nach einem Fehler weitermachen willst.
Fazit
Unit-Testing mit GoogleTest ist eine essenzielle Fähigkeit für jeden C++-Entwickler. Ob White-Box oder Black-Box – Tests geben dir Sicherheit und sparen Zeit bei der Fehlersuche. Mit den gezeigten Techniken bist du bestens gerüstet, um die Aufgaben in COP 4600 zu meistern und darüber hinaus in der Praxis professionelle Software zu entwickeln.