Tests en C++ : frameworks et outils
Les tests de qualité sont une partie essentielle du développement en C++. Contrairement aux langages dynamiques, le C++ nécessite une attention particulière à la gestion de la mémoire, à la durée de vie des objets et aux performances. Dans cet article, nous analyserons les trois frameworks les plus populaires pour les tests unitaires : Google Test, Catch2 et doctest, et aborderons également des outils supplémentaires.
Pourquoi les tests en C++ sont-ils nécessaires ?
Le C++ est un langage avec gestion manuelle de la mémoire et une puissante magie des templates. Sans tests, il est facile de commettre :
- Des fuites mémoire et des doubles libérations
- Un comportement indéfini (UB) lors de l'utilisation de pointeurs
- Des erreurs dans la surcharge des opérateurs et les constructeurs de copie
- Des régressions lors du refactoring de code legacy
Les frameworks de test automatisent la vérification et aident à trouver rapidement les problèmes.
Google Test (gtest)
Le framework le plus répandu de Google. Il prend en charge toutes les fonctionnalités principales : les macros ASSERT_* et EXPECT_*, les tests paramétrés, les fixtures (test fixtures), les death-tests.
Installation (via CMake FetchContent)
include(FetchContent)FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.14.0)FetchContent_MakeAvailable(googletest)Exemple de test simple
#include <gtest/gtest.h>
int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n - 1);}
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(3), 6);}
int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}Fixtures et tests paramétrés
class MathFixture : public ::testing::Test {protected: void SetUp() override { /* initialisation */ } void TearDown() override { /* nettoyage */ }};
// Test paramétréclass SumTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};TEST_P(SumTest, CheckSum) { auto [a, b, expected] = GetParam(); EXPECT_EQ(a + b, expected);}INSTANTIATE_TEST_SUITE_P(SumParams, SumTest, ::testing::Values( std::make_tuple(1, 1, 2), std::make_tuple(0, 0, 0) ));Avantages : immense communauté, intégration avec CI/CD, macros puissantes, support des death-tests.
Inconvénients : compilation relativement lente, taille importante du fichier exécutable.
Catch2
Un framework moderne mettant l'accent sur la lisibilité des tests. Il utilise le style BDD (Behaviour-Driven Development) et prend en charge les sections.
Installation (header-only)
Catch2 est distribué comme un seul fichier d'en-tête. Il suffit de télécharger catch_amalgamated.hpp et de l'inclure.
Exemple avec sections
#define CATCH_CONFIG_MAIN#include <catch2/catch.hpp>
int divide(int a, int b) { if (b == 0) throw std::invalid_argument("division par zéro"); return a / b;}
TEST_CASE("Tests de division", "[math]") { SECTION("Division normale") { REQUIRE(divide(10, 2) == 5); } SECTION("Division par zéro") { REQUIRE_THROWS_AS(divide(5, 0), std::invalid_argument); }}Style BDD (Given-When-Then)
SCENARIO("Opération push_back sur un vecteur", "[conteneur]") { GIVEN("Un vecteur vide") { std::vector<int> v; WHEN("Nous ajoutons un élément") { v.push_back(42); THEN("La taille devient 1") { REQUIRE(v.size() == 1); } AND_THEN("L'élément est correct") { REQUIRE(v[0] == 42);