Pourquoi Tester Rust avec Cargo Test ?
Tester votre code est essentiel en développement, mais il peut être particulièrement délicat pour les langages compilés comme Rust. Les erreurs de compilation peuvent être difficiles à repérer, et même si Rust compile votre code sans erreur, cela ne signifie pas qu'il fonctionne correctement à l'exécution.
Un cas d'usage concret : imaginez que vous êtes en train de développer une application web qui gère des requêtes HTTP. Si vous n'avez pas de tests en place, il est facile de commettre des erreurs dans votre logique de traitement des requêtes, ce qui peut entraîner des comportements imprévisibles ou des vulnérabilités de sécurité.
Prerequis
- Connaissance de base de Rust et du langage
- Rust installé sur votre système (https://www.rust-lang.org/tools/install)
- Cargo (le package manager et le build system de Rust) est généralement inclus avec l'installation de Rust
Concepts fondamentaux
Le Testeur
Cargo, le package manager de Rust, comprend un outil appelé cargo test qui permet d'exécuter les tests dans votre projet. Un test en Rust est simplement une fonction qui vérifie si une certaine condition est vraie.
Exemple
// src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
Dans cet exemple, la fonction it_works vérifie si l'expression 2 + 2 == 4 est vraie. Si elle ne l'est pas, le test échouera.
Le Hook
Les hooks sont des fonctions qui sont exécutées avant et après chaque test. Ils peuvent être utilisés pour initialiser ou détruire des ressources communes à plusieurs tests.
Exemple
// src/lib.rs
#[cfg(test)]
mod tests {
use std::fs;
#[setup]
fn setup() {
fs::write("temp.txt", "Hello, world!").unwrap();
}
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[teardown]
fn teardown() {
fs::remove_file("temp.txt").unwrap();
}
}
Dans cet exemple, la fonction setup est exécutée avant chaque test et crée un fichier temporaire. La fonction teardown est ensuite exécutée après chaque test pour supprimer le fichier.
Mise en pratique : projet fil rouge
Nous allons créer un petit gestionnaire de tâches en Rust avec des tests. Ce gestionnaire permettra d'ajouter, de lister et de marquer comme terminées les tâches.
Étape 1 : Création du Projet
cargo new task_manager
cd task_manager
Étape 2 : Création de la Structure des Fichiers
Créez un fichier task_manager.rs dans le dossier src.
// src/task_manager.rs
use std::collections::HashMap;
pub struct TaskManager {
tasks: HashMap<String, bool>,
}
impl TaskManager {
pub fn new() -> Self {
TaskManager {
tasks: HashMap::new(),
}
}
pub fn add_task(&mut self, task_name: String) {
self.tasks.insert(task_name, false);
}
pub fn list_tasks(&self) -> Vec<String> {
let mut tasks = Vec::new();
for (task, completed) in &self.tasks {
if *completed {
tasks.push(format!("{} [COMPLETED]", task));
} else {
tasks.push(format!("{}", task));
}
}
tasks
}
pub fn mark_task_completed(&mut self, task_name: String) {
if let Some(task) = self.tasks.get_mut(&task_name) {
*task = true;
}
}
}
Étape 3 : Création des Tests
Créez un fichier tests.rs dans le dossier src.
// src/tests.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_task() {
let mut task_manager = TaskManager::new();
task_manager.add_task("Task 1".to_string());
assert_eq!(task_manager.list_tasks(), vec!["Task 1"]);
}
#[test]
fn test_mark_task_completed() {
let mut task_manager = TaskManager::new();
task_manager.add_task("Task 1".to_string());
task_manager.mark_task_completed("Task 1".to_string());
assert_eq!(task_manager.list_tasks(), vec!["Task 1 [COMPLETED]"]);
}
}
Étape 4 : Exécution des Tests
cargo test
Erreurs frequentes et debugging
Erreur 1: cannot find type 'HashMap' in this scope
Cause: Vous n'avez pas importé le type HashMap.
Correction:
// src/lib.rs
use std::collections::HashMap;
Erreur 2: expected struct, found tuple
Cause: Vous avez utilisé une syntaxe incorrecte pour accéder à un élément du HashMap.
Correction:
// src/task_manager.rs
if let Some(task) = self.tasks.get_mut(&task_name) {
*task = true;
}
Erreur 3: missing lifetime specifier
Cause: Vous n'avez pas spécifié une durée de vie pour les références dans votre structure.
Correction:
// src/task_manager.rs
pub struct TaskManager<'a> {
tasks: HashMap<&'a str, bool>,
}
Pour aller plus loin
- Tests Asynchrones: Si vous travaillez avec des opérations asynchrones, Rust offre un support intégré pour les tests asynchrones via
#[tokio::test](https://docs.rs/tokio/latest/tokio/test/index.html). - Mocking: Utilisez des bibliothèques comme
mockallpour créer des doubles de fonction pour tester votre code en isolation (https://crates.io/crates/mockall). - Performance Tests: Rust permet également d'écrire des tests de performance pour mesurer l'efficacité de votre code.
Défi Pratique
Développez un petit outil CLI qui utilise le gestionnaire de tâches que vous venez de créer. L'outil devrait permettre à l'utilisateur d'ajouter, de lister et de marquer comme terminées des tâches via la ligne de commande.
Indice: Vous pouvez utiliser la bibliothèque clap pour gérer les arguments de ligne de commande (https://crates.io/crates/clap).