Wer schon länger in der C++-Welt unterwegs ist, kennt das Problem: Der Unit-Test wird irgendwann komplizierter als der eigentliche Produktionscode. Statt sich auf das zu testende Verhalten zu konzentrieren, verbringt man viel Zeit mit Boilerplate-Code, Registrierungen und Framework-spezifischen Konstrukten. Genau hier setzen GoogleTest und GoogleMock an. Sie verfolgen das Ziel, Unit-Tests einfach, lesbar und wartbar zu machen.
Viele Entwickler haben bereits Erfahrungen mit Frameworks wie CppUnit gesammelt. Diese erfüllen zwar ihren Zweck, bringen aber oft eine Menge Overhead mit sich. Testklassen müssen registriert werden, Fixtures sind häufig verpflichtend und die verwendeten Makros wirken aus heutiger Sicht teilweise sperrig. Zudem fehlen moderne Funktionen wie einfache Mocking-Möglichkeiten oder parametrisierte Tests.
GoogleTest verfolgt einen deutlich schlankeren Ansatz. Ein einfacher Test besteht aus wenigen Zeilen Code und benötigt weder manuelle Registrierung noch zusätzliche Infrastruktur. Dadurch rückt das Wesentliche wieder in den Mittelpunkt: das Verhalten der Anwendung.
Ein klassischer Beispieltest sieht etwa so aus:
TEST(CalculatorTest, SquareRoot)
{
Calculator calc;
EXPECT_EQ(3, calc.squareRoot(9));
}
GoogleTest basiert auf einigen wenigen, leicht verständlichen Konzepten. Zentral sind die sogenannten Assertions. Sie überprüfen, ob eine bestimmte Annahme erfüllt ist. Schlägt eine Assertion fehl, wird dies im Testergebnis protokolliert.
Dabei unterscheidet GoogleTest zwischen:
Diese Unterscheidung ermöglicht eine gezielte Steuerung des Testverhaltens. Während kritische Vorbedingungen häufig mit ASSERT_* überprüft werden, eignen sich EXPECT_*-Assertions für normale Fachlogik.
Zu den wichtigsten Assertions gehören:
EXPECT_EQ(a, b); EXPECT_NE(a, b); EXPECT_GT(a, b); EXPECT_TRUE(condition); EXPECT_THROW(func(), MyException);
Darüber hinaus bietet GoogleTest eine Vielzahl weiterer Vergleichs- und Exception-Assertions.
Ein häufig unterschätzter Vorteil moderner Testframeworks sind aussagekräftige Fehlermeldungen. Viele Entwickler verwenden aus Gewohnheit Konstrukte wie:
EXPECT_TRUE(value > 6);
GoogleTest empfiehlt stattdessen:
EXPECT_GT(value, 6);
Der Grund wird sichtbar, sobald ein Test fehlschlägt.
Value of: x > 6 Actual: false Expected: true
Das Framework zeigt nicht nur an, dass der Vergleich falsch war, sondern liefert die tatsächlichen Werte mit.
Expected: (x) > (6), actual: 5 vs 6
Dadurch wird die Ursachenanalyse erheblich vereinfacht. Für komplexe Objekte können sogar eigene Ausgabefunktionen definiert werden, um Debugging-Informationen lesbarer darzustellen.
In vielen Projekten benötigen mehrere Tests das gleiche Setup. Genau dafür gibt es Test Fixtures. Sie ermöglichen es, gemeinsame Initialisierungsschritte nur einmal zu definieren. Gleichzeitig erhält jeder Test seine eigene Fixture-Instanz und bleibt dadurch unabhängig von anderen Tests.
Das unterstützt das bekannte Prinzip „Don’t Repeat Yourself“ (DRY) und sorgt für sauberere Teststrukturen. Initialisierungen können dabei über Konstruktoren, Destruktoren oder die Methoden SetUp() und TearDown() erfolgen. Als Beispiel soll ein Heizregelung getestet werden. Dies muss zum einen für jeden Test instanziert werden, weiter müssen Mocks indiziert werden.
class HeatControllerTest : public testing::Test {
protected:
void SetUp() override;
void TearDown() override;
protected:
HeatingMock* _heatingMock;
std::unique_ptr<ThermometerMock> _thermometerMock;
std::unique_ptr<HeatController> _heatController;
};
void HeatControllerTest::SetUp() {
auto heating = std::make_unique<HeatingMock>();
_heatingMock = heating.get();
_thermometerMock = std::make_unique<ThermometerMock>();
_heatController = std::make_unique<HeatController>(*_thermometerMock,
std::move(heating), 1.0);
}
void HeatControllerTest::TearDown() {}
Mit diesem Setup kann nun jeder Test die erstellten Instanzen direkt verwenden ohne sich um die Instanzierung zu kümmern.
Ein besonders starkes Feature von GoogleTest sind parametrisierte Tests. Oft soll dieselbe Logik mit unterschiedlichen Eingabewerten geprüft werden. Statt dutzende nahezu identische Tests zu schreiben, definiert man den Test einmal und übergibt verschiedene Testdaten.
Ein Beispiel könnte eine Funktion isEven() sein. Statt einzelne Tests für 1, 2, 3 und weitere Zahlen zu schreiben, erstellt GoogleTest automatisch einen Testlauf pro Datensatz.
#include <gtest/gtest.h>
#include <tuple>
namespace {
bool isEven(int num) {
return (num % 2) == 0;
}
} // namespace
using TestSet = std::tuple<int, bool>;
class IsEvenTest : public testing::TestWithParam<TestSet> {};
TEST_P(IsEvenTest, isEven) {
const auto num = std::get<0>(GetParam());
const auto expectedResult = std::get<1>(GetParam());
EXPECT_EQ(isEven(num), expectedResult);
}
INSTANTIATE_TEST_SUITE_P(TestCases, IsEvenTest,
::testing::Values(std::make_tuple(1, false),
std::make_tuple(2, true),
std::make_tuple(3, false)));
Noch interessanter wird es bei Funktionen mit mehreren Parametern. Die Kombination verschiedener Eingaben führt schnell zu einer großen Anzahl möglicher Testfälle. GoogleTest unterstützt die automatische Erzeugung solcher Kombinationen und hilft dadurch, die Testabdeckung deutlich zu erhöhen, ohne den Code unnötig aufzublähen.
Neben GoogleTest gehört auch GoogleMock zum Paket. Mock-Objekte simulieren reale Abhängigkeiten und geben dem Tester die volle Kontrolle über deren Verhalten.
Dadurch lassen sich Fragen beantworten wie:
Die Definition eines Mocks ist erstaunlich kompakt:
class HeatingMock : public IHeating
{
public:
MOCK_METHOD(void, setState, (bool), (override));
MOCK_METHOD(bool, getState, (), (const, override));
};
Innerhalb des Tests können anschließend konkrete Erwartungen definiert werden.
Mit EXPECT_CALL() lassen sich Aufrufe sehr genau beschreiben:
EXPECT_CALL(heatingMock, setState(true));
Auch allgemeine Matcher sind möglich:
EXPECT_CALL(heatingMock, setState(testing::_));
Oder Bedingungen:
EXPECT_CALL(controller, setTargetTempDegCelsius(testing::Ge(100.0)));
Hier zeigt sich die enge Verwandtschaft zu den bereits bekannten Matchern aus EXPECT_THAT(). Das Konzept bleibt über das gesamte Framework hinweg konsistent und leicht verständlich.
GoogleMock erlaubt nicht nur die Prüfung von Aufrufen, sondern auch die Definition komplexer Verhaltensweisen. Mit WillOnce() können unterschiedliche Rückgabewerte für aufeinanderfolgende Aufrufe festgelegt werden. WillRepeatedly() definiert Standardverhalten für alle weiteren Aufrufe.
Ebenso lassen sich genaue Aufrufzahlen vorgeben:
EXPECT_CALL(sensor, getTemperature()).Times(4);
Sogar die Reihenfolge von Methodenaufrufen kann mit InSequence überprüft werden. Dadurch lassen sich komplexe Interaktionen zwischen Komponenten zuverlässig absichern.
GoogleTest und GoogleMock bieten einen modernen, pragmatischen Ansatz für Unit-Tests in C++. Die Frameworks reduzieren Boilerplate-Code, verbessern die Lesbarkeit von Tests und bieten gleichzeitig mächtige Funktionen wie Matcher, parametrisierte Tests und Mocking. Entwickler können sich dadurch stärker auf das eigentliche Verhalten ihrer Software konzentrieren und weniger auf die Infrastruktur rund um die Tests. Wer bisher mit älteren Frameworks gearbeitet hat oder den Einstieg in automatisierte Tests sucht, sollte GoogleTest definitiv eine Chance geben. Es macht Unit-Tests nicht nur einfacher, sondern häufig auch deutlich angenehmer.
Zum Einstieg empfiehlt sich der offizielle User Guide von GoogleTest, der zahlreiche weitere Beispiele und Best Practices enthält.
Schreiben Sie einen Kommentar