Introduction : Pourquoi tester en C++ n'est pas un luxe, mais une nécessité
Dans le monde du C++, la qualité du code est cruciale. Le langage donne au développeur un immense pouvoir sur la mémoire et les performances, mais ce pouvoir exige une grande responsabilité. Une erreur de gestion de la mémoire, une mauvaise utilisation des pointeurs ou un travail incorrect avec le multithreading peuvent entraîner des bugs difficiles à détecter, qui ne se manifestent qu'en production.
Le test est le seul moyen fiable de garantir que votre code C++ se comporte comme prévu. Dans cet article, nous parcourrons le chemin depuis les petits tests unitaires jusqu'aux vérifications complètes de bout en bout (E2E). Vous apprendrez comment structurer correctement une stratégie de test, quels outils utiliser et comment écrire des tests qui apportent réellement de la valeur.
Nous examinerons trois niveaux de la pyramide des tests : unit (tests unitaires), integration (tests d'intégration) et e2e (tests de bout en bout). Chacun répond à un objectif spécifique, et seule leur combinaison donne une confiance totale dans la fiabilité du programme.
Tests unitaires : Le fondement de la qualité
Les tests unitaires constituent le niveau le plus bas et le plus rapide. Ils vérifient des fonctions, classes ou méthodes individuelles, isolées des dépendances externes (bases de données, système de fichiers, réseau). L'objectif principal est de s'assurer que chaque unité logique de code fonctionne correctement.
Choix d'un framework pour le C++
Dans l'écosystème C++, plusieurs frameworks populaires existent pour les tests unitaires :
- Google Test (gtest) — le framework le plus répandu, développé par Google. Il prend en charge les assertions, les fixtures, les tests paramétrés et les mocks (via Google Mock).
- Catch2 — un framework léger et moderne avec une excellente syntaxe. Particulièrement adapté pour le prototypage rapide de tests.
- Boost.Test — fait partie de la bibliothèque Boost. Puissant, mais plus lourd.
Pour l'exemple, nous utiliserons Google Test, car c'est le standard de facto dans de nombreux projets C++.
Exemple de test unitaire en C++
Supposons que nous ayons une fonction simple pour calculer la factorielle :
// math_utils.h#ifndef MATH_UTILS_H#define MATH_UTILS_H
long long factorial(int n);
#endif
// math_utils.cpp#include "math_utils.h"
long long factorial(int n) { if (n < 0) throw std::invalid_argument("Negative input"); if (n == 0) return 1; long long result = 1; for (int i = 1; i <= n; ++i) { result *= i; } return result;}Écrivons maintenant un test unitaire en utilisant Google Test :
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(factorial(1), 1); EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(10), 3628800);}
TEST(FactorialTest, ThrowsOnNegative) { EXPECT_THROW(factorial(-1), std::invalid_argument);}Ce test vérifie trois scénarios : le cas limite (0), le fonctionnement normal et la gestion des erreurs. Notez les macros EXPECT_EQ et EXPECT_THROW — elles n'interrompent pas le test en cas d'erreur, mais se contentent de l'enregistrer.
Objets Mock pour l'isolation
Pour les tests unitaires, il est crucial d'isoler le code testé des dépendances externes. Pour cela, on utilise des objets mock. Google Mock (qui fait partie de Google Test) permet de créer facilement des bouchons :
// database_connector.hclass DatabaseConnector {public: virtual ~DatabaseConnector() = default; virtual bool connect(const std::string& url) = 0; virtual std::vector<int> fetchData() = 0;};
// user_service.cppclass UserService { DatabaseConnector& db;public: UserService(DatabaseConnector& db) : db(db) {} int ge