Rust a été conçu dès le départ en mettant l'accent sur la fiabilité et la sécurité, ce qui fait des tests unitaires une partie intégrante de l'écosystème du langage. Les tests sont intégrés au niveau du langage et de la chaîne d'outils (cargo test), ce qui les rend une partie organique du processus de développement.
Historique de la question
Dans les écosystèmes traditionnels (comme C/C++, Python, Java), les tests existaient en dehors du programme lui-même. En Rust, les tests sont partie intégrante du code, ils sont compilés et vérifiés comme un module à part entière. Cette synergie est accomplie grâce aux constructions linguistiques et aux caractéristiques du compilateur.
Problème
Sans tests appropriés, il est impossible de garantir la fiabilité des fonctionnalités clés. Pour des projets complexes et multmodules, la question se pose souvent : comment organiser les tests de manière pratique, sans les rendre dépendants de l'état d'autres modules et sans compliquer la structure du projet ?
Solution
Dans Rust, les tests sont placés à l'intérieur du fichier source lui-même (à l'aide de #[cfg(test)]), ou dans un dossier séparé tests pour les tests d'intégration. Des tests privés peuvent être ajoutés à chaque module, ayant accès à l'API privée.
Exemple de code :
pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add_positive() { assert_eq!(add(2, 3), 5); } #[test] fn test_add_negative() { assert_eq!(add(-1, -3), -4); } }
Caractéristiques clés :
À quoi sert use super::*; dans le module de test ?
Pour que les tests puissent accéder aux fonctions et structures du module actuel (y compris privées), on utilise généralement use super::*; dans le test.
Un #[test] peut-il être asynchrone ?
Par défaut, #[test] ne prend pas en charge async, mais en utilisant des crate externes (comme tokio ou async-std), il est possible de faire un #[test] asynchrone.
Exemple de code :
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
Les tests peuvent-ils modifier l'état global et comment l'éviter ?
Les tests en Rust s'exécutent par défaut en parallèle, donc il est impossible d'utiliser un état global mutable partagé sans synchronisation (static mut), sinon des courses se produiront.
Tous les tests utilisent une variable globale :
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
Avantages :
Inconvénients :
Chaque test est isolé, utilise des variables locales et des objets mock.
Avantages :
Inconvénients :