Et si vos tests unitaires mentaient ?
\nVous avez écrit une centaine de tests unitaires. Ils passent. Le code en C++ a l'air parfait. Mais lorsque vous lancez l'application en production, elle plante avec une erreur de segmentation à la 100500e requête. Une douleur familière ?
\nLes tests unitaires traditionnels ne vérifient que ce que vous avez vous-même imaginé. Ils sont aveugles aux scénarios qui ne vous sont pas venus à l'esprit. Les tests basés sur les propriétés (ou property-based testing, PBT) résolvent ce problème radicalement : ils ne vérifient pas des exemples spécifiques, mais prouvent des propriétés (properties) de votre code pour des millions de données d'entrée aléatoires.
\nDans cet article, nous verrons comment implémenter les tests basés sur les propriétés en C++ avec la bibliothèque RapidCheck, et nous constaterons comment ils attrapent des bugs que les tests unitaires ordinaires laissent passer. Prêts à faire vraiment souffrir votre code ? C'est parti.
\n\nTests basés sur les propriétés : la philosophie en 30 secondes
\nImaginez que vous testez une fonction de tri. Test classique : sort([3,1,2]) == [1,2,3]. Ok, on a vérifié un cas.
Approche basée sur les propriétés : « Pour toute liste d'entiers, après le tri, la longueur ne change pas, les éléments sont classés par ordre croissant, et chaque élément de la liste d'origine est présent dans la liste triée ». Le framework génère des centaines de listes aléatoires (y compris vides, avec des doublons, avec des nombres négatifs) et vérifie ces propriétés.
\nL'idée clé : vous décrivez des invariants (properties) qui doivent toujours être vrais, et non des entrées et sorties spécifiques.
\n\nQuand le PBT est-il particulièrement utile en C++ ?
\n- \n
- Algorithmes et structures de données (tris, recherche, graphes) \n
- Analyseurs syntaxiques et sérialiseurs (encodage/décodage) \n
- Fonctions mathématiques (opérations inverses, plages de valeurs) \n
- Gestion de la mémoire (fuites, double libération) \n
- Tout code où il existe des invariants clairs \n
RapidCheck : votre nouveau meilleur ami
\nEn C++, il existe plusieurs bibliothèques pour le PBT, mais RapidCheck est la plus mature et la plus pratique. Elle s'inspire de la bibliothèque Haskell QuickCheck et s'intègre parfaitement avec Google Test et Catch2.
\nL'installation est simple : c'est une bibliothèque header-only, il suffit de copier les fichiers de include/rapidcheck dans votre projet. Ou utilisez Conan/vcpkg.
Premier test : propriété d'opération inverse
\nSupposons que nous ayons une fonction qui encode une chaîne en base64 et la décode. Quelle propriété est évidente ? « Le décodage après l'encodage renvoie la chaîne d'origine ».
\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("Le décodage après l'encodage renvoie l'original",\n [](const std::string& original) {\n // Le générateur de chaîne est déjà intégré\n auto encoded = encode_base64(original);\n auto decoded = decode_base64(encoded);\n RC_ASSERT(decoded == original);\n });\n return 0;\n}\n\nQue se passe-t-il ici ? RapidCheck génère des chaînes aléatoires (y compris vides, avec Unicode, avec des octets nuls). Pour chacune, il vérifie la propriété. S'il trouve un contre-exemple — minimal (shrinking), qui casse toujours le test. C'est de l'or : vous n'obtenez pas simplement « il y a un bug », mais un cas reproductible concret.
\n\nPropriétés avancées : plus que l'égalité
\nMonotonie et idempotence
\nPour la fonction remove_duplicates :
- \n
- Idempotence : si on l'applique deux fois, le résultat ne change pas. \n
- L'ordre n'a pas d'importance : la liste résultante contient les mêmes éléments que la liste d'origine, mais sans doublons. \n
#include <vector>\n#include <algo