Leçon 15 - Tests et Validation

55 minutes Avancé P3 - Bonnes pratiques

Apprenez à tester vos fonctions et assurer la qualité de votre code.

Introduction aux tests

Les tests permettent de vérifier que votre code fonctionne correctement et de détecter les régressions lors des modifications.

Tests manuels simples

Commençons par des tests basiques intégrés directement dans votre code :

C#
class MathUtilitaires { public static int Additionner(int a, int b) { return a + b; } public static int Soustraire(int a, int b) { return a - b; } public static double Diviser(double a, double b) { if (b == 0) throw new ArgumentException("Division par zéro impossible"); return a / b; } } // Tests manuels static void TesterFonctionsMath() { Console.WriteLine("=== Tests des fonctions mathématiques ==="); // Test addition int resultatAddition = MathUtilitaires.Additionner(5, 3); if (resultatAddition == 8) { Console.WriteLine("✅ Test addition : RÉUSSI"); } else { Console.WriteLine($"❌ Test addition : ÉCHEC (attendu: 8, obtenu: {resultatAddition})"); } // Test soustraction int resultatSoustraction = MathUtilitaires.Soustraire(10, 4); if (resultatSoustraction == 6) { Console.WriteLine("✅ Test soustraction : RÉUSSI"); } else { Console.WriteLine($"❌ Test soustraction : ÉCHEC (attendu: 6, obtenu: {resultatSoustraction})"); } // Test division normale double resultatDivision = MathUtilitaires.Diviser(15, 3); if (Math.Abs(resultatDivision - 5.0) < 0.001) { Console.WriteLine("✅ Test division : RÉUSSI"); } else { Console.WriteLine($"❌ Test division : ÉCHEC (attendu: 5.0, obtenu: {resultatDivision})"); } // Test division par zéro try { MathUtilitaires.Diviser(10, 0); Console.WriteLine("❌ Test division par zéro : ÉCHEC (exception attendue)"); } catch (ArgumentException) { Console.WriteLine("✅ Test division par zéro : RÉUSSI"); } }

Fonction de test réutilisable

Créons une fonction générique pour simplifier les tests :

C#
static class TestRunner { private static int testsReussis = 0; private static int testsEchoues = 0; public static void TesterEgalite<T>(string nomTest, T attendu, T obtenu) { if (Equals(attendu, obtenu)) { Console.WriteLine($"✅ {nomTest} : RÉUSSI"); testsReussis++; } else { Console.WriteLine($"❌ {nomTest} : ÉCHEC (attendu: {attendu}, obtenu: {obtenu})"); testsEchoues++; } } public static void TesterException<T>(string nomTest, Action action) where T : Exception { try { action(); Console.WriteLine($"❌ {nomTest} : ÉCHEC (exception {typeof(T).Name} attendue)"); testsEchoues++; } catch (T) { Console.WriteLine($"✅ {nomTest} : RÉUSSI"); testsReussis++; } catch (Exception ex) { Console.WriteLine($"❌ {nomTest} : ÉCHEC (exception {typeof(T).Name} attendue, {ex.GetType().Name} obtenue)"); testsEchoues++; } } public static void AfficherResultats() { Console.WriteLine($"\n=== RÉSULTATS DES TESTS ==="); Console.WriteLine($"✅ Réussis : {testsReussis}"); Console.WriteLine($"❌ Échoués : {testsEchoues}"); Console.WriteLine($"Total : {testsReussis + testsEchoues}"); if (testsEchoues == 0) Console.WriteLine("🎉 Tous les tests sont passés !"); } public static void ReinitialiserCompteurs() { testsReussis = 0; testsEchoues = 0; } }

Tests avec notre framework simplifié

C#
static void TesterAvecFramework() { TestRunner.ReinitialiserCompteurs(); Console.WriteLine("=== Tests avec framework simplifié ==="); // Tests des fonctions mathématiques TestRunner.TesterEgalite("Addition simple", 8, MathUtilitaires.Additionner(5, 3)); TestRunner.TesterEgalite("Addition négative", -2, MathUtilitaires.Additionner(-5, 3)); TestRunner.TesterEgalite("Soustraction", 6, MathUtilitaires.Soustraire(10, 4)); // Test avec tolérance pour les nombres à virgule double resultatDivision = MathUtilitaires.Diviser(22, 7); bool divisionCorrecte = Math.Abs(resultatDivision - 3.142857) < 0.001; TestRunner.TesterEgalite("Division avec tolérance", true, divisionCorrecte); // Test d'exception TestRunner.TesterException<ArgumentException>("Division par zéro", () => MathUtilitaires.Diviser(10, 0)); TestRunner.AfficherResultats(); }

Tests de validation de données

C#
class ValidateurUtilisateur { public static bool EstEmailValide(string email) { if (string.IsNullOrWhiteSpace(email)) return false; return email.Contains("@") && email.Contains(".") && email.IndexOf("@") < email.LastIndexOf("."); } public static bool EstMotDePasseSecurise(string motDePasse) { if (string.IsNullOrWhiteSpace(motDePasse) || motDePasse.Length < 8) return false; bool aMinuscule = motDePasse.Any(char.IsLower); bool aMajuscule = motDePasse.Any(char.IsUpper); bool aChiffre = motDePasse.Any(char.IsDigit); return aMinuscule && aMajuscule && aChiffre; } public static bool EstAgeValide(int age) { return age >= 0 && age <= 150; } } static void TesterValidations() { TestRunner.ReinitialiserCompteurs(); Console.WriteLine("=== Tests de validation ==="); // Tests email TestRunner.TesterEgalite("Email valide", true, ValidateurUtilisateur.EstEmailValide("user@example.com")); TestRunner.TesterEgalite("Email sans @", false, ValidateurUtilisateur.EstEmailValide("userexample.com")); TestRunner.TesterEgalite("Email sans .", false, ValidateurUtilisateur.EstEmailValide("user@example")); TestRunner.TesterEgalite("Email vide", false, ValidateurUtilisateur.EstEmailValide("")); TestRunner.TesterEgalite("Email null", false, ValidateurUtilisateur.EstEmailValide(null)); // Tests mot de passe TestRunner.TesterEgalite("Mot de passe sécurisé", true, ValidateurUtilisateur.EstMotDePasseSecurise("MonMotDePasse123")); TestRunner.TesterEgalite("Mot de passe trop court", false, ValidateurUtilisateur.EstMotDePasseSecurise("Test1")); TestRunner.TesterEgalite("Mot de passe sans majuscule", false, ValidateurUtilisateur.EstMotDePasseSecurise("motdepasse123")); TestRunner.TesterEgalite("Mot de passe sans chiffre", false, ValidateurUtilisateur.EstMotDePasseSecurise("MotDePasse")); // Tests âge TestRunner.TesterEgalite("Âge valide", true, ValidateurUtilisateur.EstAgeValide(25)); TestRunner.TesterEgalite("Âge limite inférieure", true, ValidateurUtilisateur.EstAgeValide(0)); TestRunner.TesterEgalite("Âge limite supérieure", true, ValidateurUtilisateur.EstAgeValide(150)); TestRunner.TesterEgalite("Âge négatif", false, ValidateurUtilisateur.EstAgeValide(-1)); TestRunner.TesterEgalite("Âge trop élevé", false, ValidateurUtilisateur.EstAgeValide(200)); TestRunner.AfficherResultats(); }

Tests de performance simple

C#
static class TestPerformance { public static void MesurerTempsExecution(string nomTest, Action action, int iterations = 1) { var chronometer = System.Diagnostics.Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) { action(); } chronometer.Stop(); double tempsParIteration = chronometer.ElapsedMilliseconds / (double)iterations; Console.WriteLine($"⏱️ {nomTest}: {chronometer.ElapsedMilliseconds}ms total, " + $"{tempsParIteration:F2}ms par itération ({iterations} itérations)"); } } static void TesterPerformances() { Console.WriteLine("=== Tests de performance ==="); // Test recherche dans liste var nombres = Enumerable.Range(1, 100000).ToList(); TestPerformance.MesurerTempsExecution("Recherche linéaire", () => { nombres.Contains(99999); }, 100); // Test tri TestPerformance.MesurerTempsExecution("Tri de liste", () => { var copie = new List<int>(nombres); copie.Sort(); }, 10); // Test création objets TestPerformance.MesurerTempsExecution("Création 1000 objets", () => { for (int i = 0; i < 1000; i++) { var obj = new { Id = i, Nom = $"Objet{i}" }; } }, 100); }

Tests d'intégration simples

C#
class ServiceUtilisateur { private static List<(int id, string nom, string email)> utilisateurs = new List<(int, string, string)>(); private static int prochainId = 1; public static int CreerUtilisateur(string nom, string email) { if (!ValidateurUtilisateur.EstEmailValide(email)) throw new ArgumentException("Email invalide"); if (string.IsNullOrWhiteSpace(nom)) throw new ArgumentException("Nom requis"); int id = prochainId++; utilisateurs.Add((id, nom, email)); return id; } public static (int id, string nom, string email)? ObtenirUtilisateur(int id) { return utilisateurs.FirstOrDefault(u => u.id == id); } public static void SupprimerUtilisateur(int id) { utilisateurs.RemoveAll(u => u.id == id); } public static void ViderUtilisateurs() { utilisateurs.Clear(); prochainId = 1; } } static void TesterServiceUtilisateur() { TestRunner.ReinitialiserCompteurs(); ServiceUtilisateur.ViderUtilisateurs(); Console.WriteLine("=== Tests d'intégration - Service Utilisateur ==="); // Test création utilisateur valide int userId = ServiceUtilisateur.CreerUtilisateur("Alice", "alice@example.com"); TestRunner.TesterEgalite("Création utilisateur - ID", 1, userId); // Test récupération utilisateur var utilisateur = ServiceUtilisateur.ObtenirUtilisateur(userId); TestRunner.TesterEgalite("Récupération utilisateur - Nom", "Alice", utilisateur?.nom); TestRunner.TesterEgalite("Récupération utilisateur - Email", "alice@example.com", utilisateur?.email); // Test utilisateur inexistant var utilisateurInexistant = ServiceUtilisateur.ObtenirUtilisateur(999); TestRunner.TesterEgalite("Utilisateur inexistant", null, utilisateurInexistant); // Test création avec email invalide TestRunner.TesterException<ArgumentException>("Création avec email invalide", () => ServiceUtilisateur.CreerUtilisateur("Bob", "email-invalide")); // Test suppression ServiceUtilisateur.SupprimerUtilisateur(userId); var utilisateurSupprime = ServiceUtilisateur.ObtenirUtilisateur(userId); TestRunner.TesterEgalite("Suppression utilisateur", null, utilisateurSupprime); TestRunner.AfficherResultats(); }
Types de tests essentiels :
  • Tests unitaires : Testent une fonction isolée
  • Tests d'intégration : Testent plusieurs composants ensemble
  • Tests de validation : Vérifient les règles métier
  • Tests d'exception : Vérifient la gestion d'erreurs
  • Tests de performance : Mesurent les temps d'exécution
  • Tests de régression : S'assurent que les modifications ne cassent rien

Exercice pratique complet

Testez cette calculatrice financière :

C#
static void ExecuterTousLesTests() { Console.WriteLine("=== SUITE COMPLÈTE DE TESTS ===\n"); TesterAvecFramework(); Console.WriteLine(); TesterValidations(); Console.WriteLine(); TesterServiceUtilisateur(); Console.WriteLine(); TesterPerformances(); Console.WriteLine("\n=== Tests terminés ==="); } // Dans votre Main static void Main(string[] args) { ExecuterTousLesTests(); }
Bonnes pratiques de test :
  • Testez les cas normaux ET les cas limites
  • Utilisez des noms de test descriptifs
  • Un test = une seule assertion
  • Préparez des données de test réalistes
  • Nettoyez l'état entre les tests
  • Automatisez l'exécution des tests