Introduction
Bienvenue dans Contrôler la façon dont les tests sont exécutés. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.
Dans ce laboratoire, vous allez apprendre à contrôler le comportement des exécutions de tests en Rust en utilisant les options de ligne de commande pour cargo test et le binaire de test résultant.
Contrôler la façon dont les tests sont exécutés
De la même manière que cargo run compile votre code puis exécute le binaire résultant, cargo test compile votre code en mode test et exécute le binaire de test résultant. Le comportement par défaut du binaire produit par cargo test est d'exécuter tous les tests en parallèle et de capturer la sortie générée pendant l'exécution des tests, empêchant l'affichage de la sortie et rendant plus facile la lecture de la sortie liée aux résultats des tests. Cependant, vous pouvez spécifier des options de ligne de commande pour modifier ce comportement par défaut.
Certaines options de ligne de commande sont destinées à cargo test, et d'autres au binaire de test résultant. Pour séparer ces deux types d'arguments, vous liste les arguments destinés à cargo test suivis du séparateur -- puis ceux destinés au binaire de test. L'exécution de cargo test --help affiche les options que vous pouvez utiliser avec cargo test, et l'exécution de cargo test -- --help affiche les options que vous pouvez utiliser après le séparateur.
Exécuter les tests en parallèle ou séquentiellement
Lorsque vous exécutez plusieurs tests, par défaut, ils s'exécutent en parallèle à l'aide de threads, ce qui signifie qu'ils se terminent plus rapidement et que vous obtenez des réponses plus rapidement. Étant donné que les tests s'exécutent en même temps, vous devez vous assurer que vos tests ne dépendent pas les uns des autres ou d'un état partagé, y compris un environnement partagé, tel que le répertoire de travail actuel ou les variables d'environnement.
Par exemple, supposons que chacun de vos tests exécute du code qui crée un fichier sur le disque nommé test-output.txt et écrit des données dans ce fichier. Ensuite, chaque test lit les données dans ce fichier et affirme que le fichier contient une valeur particulière, qui est différente pour chaque test. Étant donné que les tests s'exécutent en même temps, un test peut écraser le fichier entre l'écriture et la lecture d'un autre test. Le second test échouera donc, non pas parce que le code est incorrect, mais parce que les tests se sont interférés lors de leur exécution en parallèle. Une solution est de vous assurer que chaque test écrit dans un fichier différent ; une autre solution est d'exécuter les tests un par un.
Si vous ne voulez pas exécuter les tests en parallèle ou si vous voulez un contrôle plus fin sur le nombre de threads utilisés, vous pouvez envoyer le drapeau --test-threads et le nombre de threads que vous voulez utiliser au binaire de test. Voici un exemple :
cargo test -- --test-threads=1
Nous avons défini le nombre de threads de test sur 1, ce qui indique au programme de ne pas utiliser de parallélisme. Exécuter les tests avec un seul thread prendra plus de temps que les exécuter en parallèle, mais les tests ne se interféreront pas s'ils partagent un état.
Afficher la sortie de la fonction
Par défaut, si un test réussit, la bibliothèque de tests de Rust capture tout ce qui est imprimé sur la sortie standard. Par exemple, si nous appelons println! dans un test et que le test réussit, nous ne verrons pas la sortie de println! dans le terminal ; nous verrons seulement la ligne indiquant que le test a réussi. Si un test échoue, nous verrons tout ce qui a été imprimé sur la sortie standard avec le reste du message d'erreur.
Par exemple, la Liste 11-10 a une fonction stupide qui imprime la valeur de son paramètre et renvoie 10, ainsi qu'un test qui réussit et un test qui échoue.
Nom de fichier : src/lib.rs
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {a}");
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_test_will_pass() {
let value = prints_and_returns_10(4);
assert_eq!(10, value);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(5, value);
}
}
Liste 11-10 : Tests pour une fonction qui appelle println!
Lorsque nous exécutons ces tests avec cargo test, nous verrons la sortie suivante :
running 2 tests
test tests::this_test_will_pass... ok
test tests::this_test_will_fail... FAILED
failures:
---- tests::this_test_will_fail stdout ----
1 I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
Remarquez que nulle part dans cette sortie, nous ne voyons pas I got the value 4, qui est imprimé lorsque le test qui réussit est exécuté. Cette sortie a été capturée. La sortie du test qui a échoué, I got the value 8 [1], apparaît dans la section de la sortie du résumé des tests, qui montre également la cause de l'échec du test.
Si nous voulons également voir les valeurs imprimées pour les tests réussis, nous pouvons demander à Rust de montrer également la sortie des tests réussis avec --show-output :
cargo test -- --show-output
Lorsque nous exécutons à nouveau les tests de la Liste 11-10 avec le drapeau --show-output, nous voyons la sortie suivante :
running 2 tests
test tests::this_test_will_pass... ok
test tests::this_test_will_fail... FAILED
successes:
---- tests::this_test_will_pass stdout ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
Exécuter un sous-ensemble de tests par nom
Parfois, exécuter un ensemble complet de tests peut prendre beaucoup de temps. Si vous travaillez sur du code dans une zone particulière, vous pouvez vouloir exécuter seulement les tests relatifs à ce code. Vous pouvez choisir les tests à exécuter en passant à cargo test le nom ou les noms des tests que vous voulez exécuter en tant qu'argument.
Pour démontrer comment exécuter un sous-ensemble de tests, nous allons tout d'abord créer trois tests pour notre fonction add_two, comme indiqué dans la Liste 11-11, et choisir lesquels exécuter.
Nom de fichier : src/lib.rs
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}
#[test]
fn add_three_and_two() {
assert_eq!(5, add_two(3));
}
#[test]
fn one_hundred() {
assert_eq!(102, add_two(100));
}
}
Liste 11-11 : Trois tests avec trois noms différents
Si nous exécutons les tests sans passer d'arguments, comme nous l'avons vu précédemment, tous les tests seront exécutés en parallèle :
running 3 tests
test tests::add_three_and_two... ok
test tests::add_two_and_two... ok
test tests::one_hundred... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
Exécuter un seul test
Nous pouvons passer le nom de n'importe quelle fonction de test à cargo test pour exécuter seulement ce test :
[object Object]
Seul le test avec le nom one_hundred a été exécuté ; les deux autres tests ne correspondent pas à ce nom. La sortie du test nous informe qu'il y avait plus de tests qui n'ont pas été exécutés en affichant 2 filtrés à la fin.
Nous ne pouvons pas spécifier les noms de plusieurs tests de cette manière ; seule la première valeur donnée à cargo test sera utilisée. Mais il existe un moyen d'exécuter plusieurs tests.
Filtrer pour exécuter plusieurs tests
Nous pouvons spécifier une partie du nom d'un test, et tout test dont le nom correspond à cette valeur sera exécuté. Par exemple, puisque deux de nos noms de test contiennent add, nous pouvons exécuter ces deux tests en exécutant cargo test add :
[object Object]
Cette commande a exécuté tous les tests dont le nom contient add et a filtré le test nommé one_hundred. Notez également que le module dans lequel un test apparaît devient une partie du nom du test, de sorte que nous pouvons exécuter tous les tests d'un module en filtrant sur le nom du module.
Ignorer certains tests sauf si spécifiquement demandé
Parfois, quelques tests spécifiques peuvent être très longs à exécuter, donc vous pouvez vouloir les exclure pendant la plupart des exécutions de cargo test. Au lieu de lister en tant qu'arguments tous les tests que vous voulez exécuter, vous pouvez au contraire annoter les tests longs à exécuter en utilisant l'attribut ignore pour les exclure, comme le montre ici :
Nom de fichier : src/lib.rs
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code qui prend une heure à s'exécuter
}
Après #[test], nous ajoutons la ligne #[ignore] au test que nous voulons exclure. Maintenant, lorsque nous exécutons nos tests, it_works s'exécute, mais expensive_test ne s'exécute pas :
[object Object]
La fonction expensive_test est listée comme ignorée. Si nous voulons exécuter seulement les tests ignorés, nous pouvons utiliser cargo test -- --ignored :
[object Object]
En contrôlant quels tests s'exécutent, vous pouvez vous assurer que les résultats de cargo test seront renvoyés rapidement. Lorsque vous êtes à un stade où il est pertinent de vérifier les résultats des tests ignorés et que vous avez le temps d'attendre les résultats, vous pouvez exécuter cargo test -- --ignored à la place. Si vous voulez exécuter tous les tests, qu'ils soient ignorés ou non, vous pouvez exécuter cargo test -- --include-ignored.
Sommaire
Félicitations ! Vous avez terminé le laboratoire Contrôler la façon dont les tests sont exécutés. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.