348 lines
8.2 KiB
Markdown
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)
|