web-dev-qa-db-fra.com

Comment puis-je écrire un test unitaire Rust qui garantit qu'une panique s'est produite?

J'ai une fonction Rust qui panics sous certaines conditions et je souhaite écrire un cas de test pour valider si la fonction panique ou non. Je n'ai rien trouvé sauf le Macros assert! Et assert_eq!. Existe-t-il un mécanisme pour tester cela?

Je pourrais engendrer une nouvelle tâche et vérifier si cette tâche panique ou non. Est-ce que ça fait du sens?


Le retour d'un Result<T, E> Ne convient pas dans mon cas.

Je souhaite ajouter la prise en charge de la caractéristique Add à un type Matrix que j'implémente. La syntaxe idéale pour un tel ajout ressemblerait à:

let m = m1 + m2 + m3;

m1, m2, m3 sont toutes des matrices. Par conséquent, le type de résultat de add doit être Matrix. Quelque chose comme ce qui suit serait trop cryptique:

let m = ((m1 + m2).unwrap() + m3).unwrap()

Dans le même temps, la fonction add() doit valider que les deux matrices ajoutées ont la même dimension. Ainsi, add() doit paniquer si les dimensions ne correspondent pas. L'option disponible est panic!().

57
Shailesh Kumar

Vous pouvez trouver la réponse dans la section testing du livre Rust. Plus précisément, vous voulez #[should_panic] attribut:

#[test]
#[should_panic]
fn test_invalid_matrices_multiplication() {
    let m1 = Matrix::new(3, 4);  // assume these are dimensions
    let m2 = Matrix::new(5, 6);
    m1 * m2
}
80
Vladimir Matveev

Comme Francis Gagné l'a mentionné dans sa réponse, je trouve également que l'attribut #[should_panic] N'est pas suffisamment fin pour des tests plus complexes - par exemple, si ma configuration de test échoue pour une raison quelconque (c'est-à-dire que j'ai écrit un mauvais test), je veux qu'une panique soit considérée comme un échec!

À partir de Rust 1.9.0, std::panic::catch_unwind() est disponible. Il vous permet de mettre le code que vous prévoyez de paniquer dans une fermeture, et seulement paniques émises par ce code sera considéré comme attendu (c'est-à-dire un test réussi).

#[test]
fn test_something() {
    ... //<-- Any panics here will cause test failure (good)
    let result = std::panic::catch_unwind(|| <expected_to_panic_operation_here>);
    assert!(result.is_err());  //probe further for specific error type here, if desired
}

Notez qu'il ne peut pas attraper les paniques qui ne se déroulent pas (par exemple std::process::abort()).

30
U007D

Si vous voulez affirmer que seule une partie spécifique de la fonction de test échoue, utilisez std::panic::catch_unwind() et vérifiez qu'elle renvoie un Err, par exemple avec is_err() . Dans les fonctions de test complexes, cela permet de garantir que le test ne passe pas par erreur en raison d'une défaillance précoce.

Plusieurstests dans la bibliothèque standard Rust elle-même utilise cette technique.

14
Francis Gagné

En complément: la solution proposée par @ U007D fonctionne également dans les doctests:

/// My identity function that panic for an input of 42.
///
/// ```
/// assert_eq!(my_crate::my_func(23), 23);
///
/// let result = std::panic::catch_unwind(|| my_crate::my_func(42));
/// assert!(result.is_err());
/// ```
pub fn my_func(input: u32) -> u32 {
    if input == 42 {
        panic!("Error message.");
    } else {
        input
    }
}
4
m00am