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

348 lines
8.2 KiB
Markdown

# 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
```bash
cargo test
```
### Exécuter un fichier de tests spécifique
```bash
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
```bash
cargo test test_good_pronounceable_words
```
### Exécuter avec sortie détaillée
```bash
cargo test -- --nocapture
```
### Exécuter avec affichage des tests ignorés
```bash
cargo test -- --ignored
```
### Voir la couverture de test (avec tarpaulin)
```bash
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.
```rust
let rng = StdRng::seed_from_u64(42); // Reproductible
```
### 2. Tests descriptifs
Les noms de tests décrivent clairement ce qui est testé :
```rust
#[test]
fn test_generation_produces_unique_anagrams() { ... }
#[test]
fn test_common_consonant_clusters_not_penalized() { ... }
```
### 3. Arrange-Act-Assert
Structure claire des tests :
```rust
#[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 :
```rust
#[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](tests/analyzer_tests.rs) :
```rust
#[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](tests/generator_tests.rs) :
```rust
#[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](tests/integration_tests.rs) :
```rust
#[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 :
```rust
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 :
```yaml
# .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
```bash
cargo test -- --nocapture --test-threads=1
```
### Exécuter avec le debugger
```bash
rust-gdb --args target/debug/deps/analyzer_tests-* test_name
```
### Logs de débogage
Utiliser `dbg!()` temporairement dans les tests :
```rust
#[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/` :
```rust
// 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
- [The Rust Book - Testing](https://doc.rust-lang.org/book/ch11-00-testing.html)
- [Cargo Test Documentation](https://doc.rust-lang.org/cargo/commands/cargo-test.html)
- [Integration Testing in Rust](https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html)