Phase 5Projet pratique

#19 Tests

#[test], assert, cargo test

Votre premier test

En Rust, les tests sont des fonctions annotées avec #[test]. Ils vivent dans le même fichier que le code qu'ils testent, dans un module spécial tests.

Premier test
// src/lib.rs

pub fn addition(a: i32, b: i32) -> i32 {
    a + b
}

// Le module de tests
#[cfg(test)]        // compilé uniquement pour les tests
mod tests {
    use super::*;   // importer les fonctions du module parent

    #[test]
    fn test_addition() {
        let resultat = addition(2, 3);
        assert_eq!(resultat, 5);
    }
}

L'attribut #[cfg(test)] indique que ce module n'est compilé que pendant cargo test. Il n'alourdit pas votre binaire final.

Les macros assert

Rust fournit plusieurs macros pour vérifier les résultats dans vos tests.

Les macros assert
#[cfg(test)]
mod tests {
    #[test]
    fn test_assert_basique() {
        // assert! : vérifie qu'une condition est vraie
        assert!(2 + 2 == 4);
        assert!(true);
    }

    #[test]
    fn test_assert_eq_ne() {
        // assert_eq! : vérifie l'égalité
        assert_eq!(2 + 2, 4);
        assert_eq!("bonjour", "bonjour");

        // assert_ne! : vérifie la non-égalité
        assert_ne!(2 + 2, 5);
    }

    #[test]
    fn test_messages_personnalises() {
        let age = 25;
        // Message personnalisé en cas d'échec
        assert!(age >= 18, "L'âge {} est inférieur à 18", age);
        assert_eq!(age, 25, "L'âge devrait être 25, pas {}", age);
    }

    #[test]
    #[should_panic]  // Ce test DOIT paniquer pour réussir
    fn test_division_par_zero() {
        diviser(10, 0);
    }

    #[test]
    #[should_panic(expected = "division par zéro")]
    fn test_panic_message() {
        diviser(10, 0); // doit paniquer avec ce message
    }

    fn diviser(a: i32, b: i32) -> i32 {
        if b == 0 {
            panic!("division par zéro");
        }
        a / b
    }
}

Tester avec Result

Les tests peuvent aussi retourner un Result, ce qui permet d'utiliser l'opérateur ? au lieu de unwrap().

Tests avec Result
#[cfg(test)]
mod tests {
    use std::num::ParseIntError;

    #[test]
    fn test_parse_nombre() -> Result<(), ParseIntError> {
        let nombre: i32 = "42".parse()?;
        assert_eq!(nombre, 42);
        Ok(())  // le test réussit si Ok(()) est retourné
    }

    #[test]
    fn test_operations() -> Result<(), String> {
        let x = 10;
        let y = 20;

        if x + y != 30 {
            return Err(String::from("La somme devrait être 30"));
        }

        Ok(())
    }
}

Ignorer des tests

Ignorer des tests
#[test]
#[ignore]  // Ce test est ignoré par défaut
fn test_lent() {
    // Opération qui prend du temps...
    std::thread::sleep(std::time::Duration::from_secs(10));
    assert!(true);
}

// Pour exécuter les tests ignorés :
// $ cargo test -- --ignored
// Pour exécuter TOUS les tests (y compris ignorés) :
// $ cargo test -- --include-ignored

Tests d'intégration

Les tests d'intégration vivent dans un dossier tests/ à la racine du projet. Ils testent votre bibliothèque comme un utilisateur externe.

Structure du projet
mon-projet/
├── Cargo.toml
├── src/
│   └── lib.rs       # code de la bibliothèque
└── tests/
    ├── integration_test.rs  # test d'intégration
    └── common/
        └── mod.rs   # utilitaires partagés entre tests
tests/integration_test.rs
// Pas besoin de #[cfg(test)] ici !
// Le dossier tests/ est automatiquement en mode test.

use testing_demo;  // importer votre crate

#[test]
fn test_addition_integration() {
    // Tester l'API publique de votre bibliothèque
    assert_eq!(testing_demo::addition(10, 20), 30);
}

#[test]
fn test_workflow_complet() {
    let a = testing_demo::addition(5, 5);
    let b = testing_demo::addition(a, 10);
    assert_eq!(b, 20);
}
tests/common/mod.rs
// Utilitaires partagés entre tests d'intégration
pub fn setup() {
    // Préparer l'environnement de test
    println!("Configuration du test...");
}

pub fn donnees_test() -> Vec<i32> {
    vec![1, 2, 3, 4, 5]
}

Exécuter les tests

cargo test est la commande principale. Elle offre de nombreuses options pour filtrer et configurer l'exécution des tests.

Commandes utiles
# Exécuter tous les tests
$ cargo test

# Afficher les println! pendant les tests
$ cargo test -- --nocapture

# Exécuter un test spécifique par nom
$ cargo test test_addition

# Exécuter les tests qui contiennent un motif
$ cargo test division

# Exécuter les tests d'un module spécifique
$ cargo test tests::

# Exécuter sur un seul thread (séquentiel)
$ cargo test -- --test-threads=1

# Exécuter seulement les tests d'intégration
$ cargo test --test integration_test

# Lister tous les tests sans les exécuter
$ cargo test -- --list

À vous de jouer

Essayez les commandes ci-dessous pour exécuter les tests :

terminal — cargo
user@stemlegacy:~/testing-demo$