Workflow TDD en Rust
Trois Lois du TDD
- N'écris PAS de code production sans un test défaillant
- Écris seulement assez de test pour échouer (y compris les erreurs de compilation)
- Écris seulement assez de code production pour passer le test défaillant
Cycle : RED (test échoue) -> GREEN (minimum pour passer) -> REFACTOR (nettoyage, cargo test)
Étapes Red-Green-Refactor
1. Écris le test dans #[cfg(test)] mod tests du MÊME fichier
2. cargo test MODULE::tests::test_name -- doit ÉCHOUER (red)
3. Implémente le minimum dans la fonction
4. cargo test MODULE::tests::test_name -- doit RÉUSSIR (green)
5. Refactorise si nécessaire, relance cargo test (toujours green)
6. cargo fmt && cargo clippy --all-targets && cargo test (portail final)
Ne saute jamais l'étape 2. Si le test passe immédiatement, il ne teste rien.
Patterns de Tests Idiomatiques en Rust
| Pattern | Utilisation | Quand |
|---|---|---|
| Arrange-Act-Assert | Structure de base pour chaque test | Toujours |
assert_eq! / assert! |
Comparaison directe / booléens | Valeurs déterministes |
assert!(result.is_err()) |
Test du chemin d'erreur | Entrées invalides |
Result<()> type de retour |
Tests avec opérateur ? |
Fonctions défaillibles |
#[should_panic] |
Panique attendue | Invariants, préconditions |
tempfile::NamedTempFile |
Tests fichier/I/O | Code dépendant du système de fichiers |
Patterns par Type de Code
| Type de Code | Pattern de Test | Exemple |
|---|---|---|
| Fonction pure (str -> str) | Littéral d'entrée -> assert sortie | assert_eq!(truncate("hello", 3), "...") |
| Parsing/filtrage | Chaîne brute -> filtrer -> contains/not-contains | assert!(filter(raw).contains("expected")) |
| Validation/sécurité | Entrées limites -> assert booléen | assert!(!is_valid("../etc/passwd")) |
| Gestion d'erreur | Mauvaise entrée -> is_err() |
assert!(parse("garbage").is_err()) |
| Roundtrip struct/enum | Construire -> sérialiser -> désérialiser -> égal | assert_eq!(from_str(to_str(x)), x) |
Convention de Nommage
test_{fonction}_{scenario}
test_{fonction}_{type_entree}
Exemples : test_truncate_edge_case, test_parse_invalid_input, test_filter_empty_string
Quand NE PAS utiliser le TDD pur
- Fonctions appelant
Command::new()-> teste le parser, pas l'exécution std::process::exit()-> refactorise enResultd'abord, puis teste le Result- I/O direct (SQLite, réseau) -> utilise tempfile/mock ou teste la logique pure séparément
- Wiring main/CLI -> couvert par les tests d'intégration/smoke tests
Portail Pré-Commit
cargo fmt --all --check
cargo clippy --all-targets
cargo test
Les 3 doivent passer. Aucune exception. Aucun #[allow(...)] sans justification documentée.