Files
anagram-generator/docs/TESTING.md
2025-11-06 22:34:21 +01:00

8.2 KiB

Guide de test - Anagram Generator

Structure des tests

Le projet utilise une architecture de tests modulaire avec les tests séparés du code source. Tous les tests sont situés dans le répertoire tests/ à la racine du projet.

tests/
├── analyzer_tests.rs       # Tests pour PronounceabilityAnalyzer
├── generator_tests.rs      # Tests pour AnagramGenerator
├── types_tests.rs          # Tests pour Anagram et PronouncabilityScore
└── integration_tests.rs    # Tests d'intégration end-to-end

Types de tests

Tests unitaires par module

analyzer_tests.rs

Tests du module analyzer qui vérifient :

  • Le scoring des mots prononçables
  • La classification des caractères (voyelles/consonnes)
  • La détection des clusters de consonnes communs
  • Les pénalités pour différents patterns phonétiques
  • Les bonus pour bonne alternance voyelle-consonne

Nombre de tests : 11

generator_tests.rs

Tests du module generator qui vérifient :

  • La génération de valides anagrammes
  • Le respect du score minimum
  • L'unicité des anagrammes générés
  • Le tri par score
  • L'exclusion du mot original
  • La normalisation de l'entrée
  • Le pattern builder de configuration

Nombre de tests : 9

types_tests.rs

Tests des types de domaine qui vérifient :

  • La création et manipulation de PronouncabilityScore
  • Les opérations saturantes (add/sub)
  • Le clamping des valeurs (0-100)
  • La création et comparaison d'Anagram
  • Le tri des anagrammes par score

Nombre de tests : 10

Tests d'intégration

integration_tests.rs

Tests end-to-end qui vérifient :

  • Le flux complet de génération d'anagrammes
  • L'intégration entre l'analyseur et le générateur
  • La personnalisation du scorer via le trait
  • La configuration avancée
  • Les cas d'usage réels

Nombre de tests : 6

Exécution des tests

Exécuter tous les tests

cargo test

Exécuter un fichier de tests spécifique

cargo test --test analyzer_tests
cargo test --test generator_tests
cargo test --test types_tests
cargo test --test integration_tests

Exécuter un test particulier

cargo test test_good_pronounceable_words

Exécuter avec sortie détaillée

cargo test -- --nocapture

Exécuter avec affichage des tests ignorés

cargo test -- --ignored

Voir la couverture de test (avec tarpaulin)

cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Métriques de tests

  • Total de tests : 36 tests

    • Tests d'analyse : 11
    • Tests de génération : 9
    • Tests de types : 10
    • Tests d'intégration : 6
  • Couverture estimée : ~90% du code

  • Temps d'exécution : < 2 secondes pour tous les tests

Principes de test appliqués

1. Tests indépendants

Chaque test est autonome et n'affecte pas les autres. Utilisation de seeds fixes pour les générateurs aléatoires quand nécessaire.

let rng = StdRng::seed_from_u64(42); // Reproductible

2. Tests descriptifs

Les noms de tests décrivent clairement ce qui est testé :

#[test]
fn test_generation_produces_unique_anagrams() { ... }

#[test]
fn test_common_consonant_clusters_not_penalized() { ... }

3. Arrange-Act-Assert

Structure claire des tests :

#[test]
fn test_score_saturating_operations() {
    // Arrange
    let score = PronouncabilityScore::new(80);

    // Act
    let increased = score.saturating_add(30);

    // Assert
    assert_eq!(increased.value(), 100);
}

4. Tests du comportement, pas de l'implémentation

Focus sur le comportement observable plutôt que les détails d'implémentation.

5. Tests de cas limites

Couverture des cas limites et d'erreur :

#[test]
fn test_empty_string_scores_zero() { ... }

#[test]
fn test_score_value_clamped_to_range() { ... }

#[test]
fn test_generation_with_repeated_letters() { ... }

Écrire de nouveaux tests

Pour ajouter un test d'analyse

Créer un nouveau test dans tests/analyzer_tests.rs :

#[test]
fn test_new_phonetic_pattern() {
    let analyzer = PronounceabilityAnalyzer::with_defaults();

    // Test du comportement attendu
    assert!(analyzer.score("example").value() > expected_threshold);
}

Pour ajouter un test de génération

Créer un nouveau test dans tests/generator_tests.rs :

#[test]
fn test_new_generation_behavior() {
    let rng = StdRng::seed_from_u64(42);
    let scorer = PronounceabilityAnalyzer::with_defaults();
    let mut generator = AnagramGenerator::new(rng, scorer);

    let config = GenerationConfig::new(min_score, max_attempts);
    let anagrams = generator.generate("test", count, &config);

    // Vérifications
    assert!(!anagrams.is_empty());
}

Pour ajouter un test d'intégration

Créer un nouveau test dans tests/integration_tests.rs :

#[test]
fn test_end_to_end_new_feature() {
    // Setup complet du système
    let rng = thread_rng();
    let scorer = PronounceabilityAnalyzer::with_defaults();
    let mut generator = AnagramGenerator::new(rng, scorer);

    // Test du flux complet
    let config = GenerationConfig::new(50, 1000);
    let result = generator.generate("word", 5, &config);

    // Vérifications end-to-end
    assert!(result.meets_requirements());
}

Tests avec mocks et stubs

Pour tester l'injection de dépendances avec des scorers personnalisés :

struct MockScorer {
    fixed_score: u32,
}

impl PronounceabilityScorer for MockScorer {
    fn score(&self, _text: &str) -> PronouncabilityScore {
        PronouncabilityScore::new(self.fixed_score)
    }
}

#[test]
fn test_with_mock_scorer() {
    let rng = thread_rng();
    let scorer = MockScorer { fixed_score: 100 };
    let mut generator = AnagramGenerator::new(rng, scorer);

    // Tous les anagrammes auront un score de 100
    let config = GenerationConfig::new(99, 100);
    let anagrams = generator.generate("test", 5, &config);

    assert!(anagrams.len() > 0);
    for anagram in anagrams {
        assert_eq!(anagram.score().value(), 100);
    }
}

Stratégie de test pour les contributions

Lors de l'ajout de nouvelles fonctionnalités :

  1. Écrire les tests d'abord (TDD)

    • Définir le comportement attendu
    • Écrire les tests qui échouent
    • Implémenter la fonctionnalité
    • Vérifier que les tests passent
  2. Couvrir les cas nominaux et d'erreur

    • Cas nominal (happy path)
    • Cas limites (edge cases)
    • Cas d'erreur
  3. Maintenir l'isolation

    • Pas de dépendances entre tests
    • Pas d'état partagé
  4. Tests rapides

    • Éviter les I/O inutiles
    • Utiliser des seeds fixes pour le random
    • Pas de sleep() ou d'attente

Continuous Integration

Le projet est prêt pour CI/CD. Configuration recommandée :

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
      - run: cargo test --all-features
      - run: cargo test --doc

Debugging des tests

Afficher la sortie des tests

cargo test -- --nocapture --test-threads=1

Exécuter avec le debugger

rust-gdb --args target/debug/deps/analyzer_tests-* test_name

Logs de débogage

Utiliser dbg!() temporairement dans les tests :

#[test]
fn test_debug() {
    let score = analyzer.score("test");
    dbg!(score); // Affiche la valeur
    assert!(score.value() > 50);
}

Benchmark (optionnel)

Pour des benchmarks de performance, créer benches/ :

// benches/generation_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn benchmark_generation(c: &mut Criterion) {
    c.bench_function("generate 10 anagrams", |b| {
        b.iter(|| {
            // Code à benchmarker
        });
    });
}

criterion_group!(benches, benchmark_generation);
criterion_main!(benches);

Ressources