Was, wenn Ihre Unit-Tests lügen?
\nSie haben hundert Unit-Tests geschrieben. Sie bestehen. Der C++-Code sieht perfekt aus. Aber wenn Sie die Anwendung in der Produktion starten, stürzt sie mit einem Segmentation Fault bei der 100500. Anfrage ab. Kennen Sie den Schmerz?
\nTraditionelle Unit-Tests überprüfen nur das, was Sie sich selbst ausgedacht haben. Sie sind blind für Szenarien, die Ihnen nicht in den Sinn gekommen sind. Property-basiertes Testen (oder Property-based Testing, PBT) löst dieses Problem radikal: Es überprüft keine konkreten Beispiele, sondern beweist Eigenschaften (Properties) Ihres Codes für Millionen zufälliger Eingabedaten.
\nIn diesem Artikel werden wir uns ansehen, wie man Property-basiertes Testen in C++ mit der Bibliothek RapidCheck implementiert, und sehen, wie es Bugs fängt, die normale Unit-Tests übersehen. Bereit, Ihren Code richtig leiden zu lassen? Los geht's.
\n\nProperty-basiertes Testen: Philosophie in 30 Sekunden
\nStellen Sie sich vor, Sie testen eine Sortierfunktion. Ein normaler Test: sort([3,1,2]) == [1,2,3]. Okay, ein Fall geprüft.
Property-basierter Ansatz: „Für jede Liste ganzer Zahlen ändert sich nach dem Sortieren die Länge nicht, die Elemente sind aufsteigend angeordnet, und jedes Element der ursprünglichen Liste ist in der sortierten Liste vorhanden“. Das Framework generiert hunderte zufällige Listen (einschließlich leerer, mit Duplikaten, mit negativen Zahlen) und überprüft diese Eigenschaften.
\nDie Kernidee: Sie beschreiben Invarianten (Properties), die immer gelten müssen, und nicht konkrete Ein- und Ausgaben.
\n\nWann ist PBT in C++ besonders nützlich?
\n- \n
- Algorithmen und Datenstrukturen (Sortierungen, Suche, Graphen) \n
- Parser und Serialisierer (Kodierung/Dekodierung) \n
- Mathematische Funktionen (inverse Operationen, Wertebereiche) \n
- Speicherverwaltung (Speicherlecks, doppeltes Freigeben) \n
- Jeder Code mit klaren Invarianten \n
RapidCheck: Ihr neuer bester Freund
\nIn C++ gibt es mehrere Bibliotheken für PBT, aber RapidCheck ist die ausgereifteste und benutzerfreundlichste. Sie ist von der Haskell-Bibliothek QuickCheck inspiriert und lässt sich hervorragend in Google Test und Catch2 integrieren.
\nDie Installation ist einfach: Es ist eine Header-only-Bibliothek, Sie müssen nur die Dateien aus include/rapidcheck in Ihr Projekt kopieren. Oder verwenden Sie Conan/vcpkg.
Erster Test: Eigenschaft einer inversen Operation
\nAngenommen, wir haben eine Funktion, die einen String in base64 kodiert und wieder dekodiert. Welche Eigenschaft ist offensichtlich? „Dekodierung nach Kodierung gibt den ursprünglichen String zurück“.
\n#include <rapidcheck.h>\n#include <string>\n\nstd::string encode_base64(const std::string& input);\nstd::string decode_base64(const std::string& input);\n\nint main() {\n rc::check("Dekodierung nach Kodierung gibt das Original zurück",\n [](const std::string& original) {\n // String-Generator ist bereits eingebaut\n auto encoded = encode_base64(original);\n auto decoded = decode_base64(encoded);\n RC_ASSERT(decoded == original);\n });\n return 0;\n}\n\nWas passiert hier? RapidCheck generiert zufällige Strings (einschließlich leerer, mit Unicode, mit Null-Bytes). Für jeden wird die Eigenschaft überprüft. Wenn ein Gegenbeispiel gefunden wird – minimal (Shrinking), das den Test immer noch bricht. Das ist Gold wert: Sie erhalten nicht nur „es gibt einen Bug“, sondern einen konkreten reproduzierbaren Fall.
\n\nFortgeschrittene Eigenschaften: Mehr als Gleichheit
\nMonotonie und Idempotenz
\nFür die Funktion remove_duplicates:
- \n
- Idempotenz: Bei zweimaliger Anwendung ändert sich das Ergebnis nicht. \n
- Reihenfolge ist unwichtig: Die resultierende Liste enthält dieselben Elemente wie die ursprüngliche, jedoch ohne Duplikate. \n
#include <vector>\n#include <algo