Gérer l’exécution des tests
Comme cargo run qui compile votre code et qui exécute ensuite le binaire qui en résulte, cargo test compile votre code en mode test et lance le binaire de tests qu’il produit. Le comportement par défaut des binaires produits par cargo test est de lancer tous les tests en parallèle et de capturer la sortie pendant l’exécution des tests, ce qui lui évite d’être affichée sur l’écran pendant ce temps, facilitant la lecture des messages relatifs aux résultats de l’exécution des tests. Vous pouvez cependant rajouter des options en ligne de commande pour changer ce comportement par défaut.“
Certaines options de la ligne de commande s’appliquent à cargo test, et certaines au binaire de tests qui en résulte. Pour séparer ces types d’arguments, il faut lister les arguments qui s’appliquent à cargo test, suivis du séparateur --, puis ajouter ceux qui s’appliquent au binaire de tests. L’exécution de cargo test --help affiche les options que vous pouvez utiliser sur cargo test, et l’exécution de cargo test -- --help affiche les options que vous pouvez utiliser après le séparateur --. Ces options sont également décrites dans la section “Tests” du Livre de rustc.
Lancer les tests en parallèle ou en séquence
Lorsque vous lancez de nombreux tests, par défaut ils s’exécutent en parallèle dans des tâches (fils d’exécution, ou threads), ce qui veut dire que tous les tests vont finir de s’exécuter plus rapidement, et que vous avez des retours plus tôt. Comme les tests s’exécutent en même temps, il faut vous assurer qu’ils ne dépendent pas les uns des autres ou d’un état partagé, y compris un environnement partagé, comme le répertoire de travail actuel ou des variables d’environnement.
Par exemple, disons que chacun de vos tests exécute du code qui crée un fichier test-sortie.txt sur le disque dur et qu’il écrit quelques données dans ce fichier. Ensuite, chaque test lit les données de ce fichier et vérifie que le fichier contient une valeur précise, qui est différente dans chaque test. Comme les tests sont lancés en même temps, un test risque d’écraser le contenu du fichier entre le moment où un autre test lit et écrit sur ce fichier. Le second test va ensuite échouer, non pas parce que le code est incorrect mais parce que les tests se sont perturbés mutuellement pendant qu’ils s’exécutaient en parallèle. Une solution serait de s’assurer que chaque test écrit dans un fichier différent ; une autre serait de lancer les tests les uns après les autres.
Si vous ne souhaitez pas exécuter les tests en parallèle ou si vous voulez un contrôle plus précis du nombre de tâches utilisées, vous pouvez utiliser l’option --test-threads suivie du nombre de tâches que vous souhaitez que le binaire de test exécute en parallèle. Regardez cet exemple :
$ cargo test -- --test-threads=1
Nous avons réglé le nombre de tâches à 1, ce qui indique au programme de ne pas utiliser le parallélisme. Exécuter ces tests en n’effectuant qu’une seule tâche à la fois va prendre plus de temps que de les lancer en parallèle, mais cela assure que les tests ne vont pas s’influencer mutuellement s’ils partagent le même état.
Afficher la sortie de la fonction
Par défaut, si un test réussit, la bibliothèque de test de Rust récupère tout ce qui est affiché 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 correspondant au println! dans le terminal ; on verra seulement la ligne qui indique que le test a réussi. Si un test échoue, nous verrons ce qui a été affiché sur la sortie standard avec le reste des messages d’erreur.
Par exemple, l’encart 11-10 a une fonction stupide qui affiche la valeur de ses paramètres et retourne 10, ainsi qu’un test qui réussit et un test qui échoue.
fn affiche_et_retourne_10(a: i32) -> i32 {
println!("J'ai obtenu la valeur {a}");
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ce_test_reussit() {
let valeur = affiche_et_retourne_10(4);
assert_eq!(valeur, 10);
}
#[test]
fn ce_test_echoue() {
let valeur = affiche_et_retourne_10(8);
assert_eq!(valeur, 5);
}
}
println!Lorsque nous lançons ces tests avec cargo test, nous voyons cette sortie :
$ cargo test
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::ce_test_echoue ... FAILED
test tests::ce_test_reussit ... ok
failures:
---- tests::ce_test_echoue stdout ----
J'ai obtenu la valeur 8
thread 'tests::ce_test_echoue' panicked at src/lib.rs:19:9
assertion `left == right` failed
left: 10
right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::ce_test_echoue
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
Remarquez que nous n’avons jamais vu J'ai obtenu la valeur 4 dans cette sortie, qui est affichée lors de l’exécution du test qui réussit. Cette sortie a été capturée. La sortie pour le test qui a échoué, J'ai obtenu la valeur 8, s’affiche dans la section de la sortie correspondante au résumé des tests, qui affiche aussi les causes de l’échec du test.
Si nous voulons aussi voir les valeurs affichées pour les tests réussis, nous pouvons demander à Rust d’afficher également la sortie des tests fructueux en lui rajoutant --show-output.
$ cargo test -- --show-output
Lorsque nous lançons à nouveau les tests de l’encart 11-10 avec l’option --show-output, nous voyons la sortie suivante :
$ cargo test -- --show-output
Compiling silly-function v0.1.0 (file:///projects/silly-function)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
running 2 tests
test tests::ce_test_echoue ... FAILED
test tests::ce_test_reussit ... ok
successes:
---- tests::ce_test_reussit stdout ----
J'ai obtenu la valeur 4
successes:
tests::ce_test_reussit
failures:
---- tests::ce_test_echoue stdout ----
J'ai obtenu la valeur 8
thread 'tests::ce_test_echoue' panicked at src/lib.rs:19:9
assertion `left == right` failed
left: 10
right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::ce_test_echoue
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
Exécuter un sous-ensemble de tests en fonction de son nom
Lancer une suite de tests entière peut parfois prendre beaucoup de temps. Si vous travaillez sur du code d’un périmètre bien défini, vous pourriez avoir besoin d’exécuter uniquement les tests relatifs à ce code. Vous pouvez choisir quels tests exécuter en envoyant le ou les noms du ou des tests que vous souhaitez exécuter en argument de cargo test.
Dans le but de démontrer comment lancer un sous-ensemble de tests, nous allons commencer par créer trois tests pour notre fonction ajouter_deux dans l’encart 11-11, et choisir lesquels nous allons exécuter.
pub fn ajouter_deux(a: u64) -> u64 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ajouter_deux_a_deux() {
let resultat = ajouter_deux(2);
assert_eq!(resultat, 4);
}
#[test]
fn ajouter_deux_a_trois() {
let resultat = ajouter_deux(3);
assert_eq!(resultat, 5);
}
#[test]
fn cent() {
let resultat = ajouter_deux(100);
assert_eq!(resultat, 102);
}
}
Si nous exécutons les tests sans ajouter d’arguments, comme nous l’avons vu précédemment, tous les tests vont s’exécuter en parallèle :
$ cargo test
Compiling addition v0.1.0 (file:///projects/addition)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 3 tests
test tests::ajouter_deux_a_trois ... ok
test tests::ajouter_deux_a_deux ... ok
test tests::cent ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests addition
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Exécuter des tests individuellement
Nous pouvons donner le nom de n’importe quelle fonction de test à cargo test afin d’exécuter uniquement ce test :
$ cargo test cent
Compiling addition v0.1.0 (file:///projects/addition)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::cent ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
Le test avec le nom cent est le seul exécuté ; les deux autres tests ne correspondent pas à ce nom. La sortie du test nous indique que nous avons d’autres tests qui n’ont pas tourné en affichant 2 filtered out à la fin de la ligne de résumé.
Nous ne pouvons pas renseigner plusieurs noms de tests de cette manière ; il n’y a que la première valeur fournie à cargo test qui sera utilisée. Mais il existe un moyen d’exécuter plusieurs tests.
Filtrer pour exécuter plusieurs tests
Nous pouvons ne renseigner qu’une partie d’un nom de test, et tous les tests dont les noms correspondent à cette valeur vont être exécutés. Par exemple, comme deux de nos noms de tests contiennent ajouter, nous pouvons exécuter ces deux en lançant cargo test ajouter :
$ cargo test ajouter
Compiling addition v0.1.0 (file:///projects/addition)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::ajouter_deux_a_trois ... ok
test tests::ajouter_deux_a_deux ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Cette commande a lancé tous les tests qui contiennent ajouter dans leur nom et a filtré le test cent. Notez aussi que le module dans lequel un test est présent fait partie du nom du test, ainsi nous pouvons exécuter tous les tests d’un module en filtrant avec le nom du module.
Ignorer des tests sauf s’ils sont demandés explicitement
Parfois, certains tests spécifiques peuvent prendre beaucoup de temps à s’exécuter, de sorte que vous voulez les exclure de la majorité des exécutions de cargo test. Plutôt que de lister en argument tous les tests que vous souhaitez exécuter, vous pouvez plutôt faire une annotation sur les tests qui prennent du temps en utilisant l’attribut ignore pour les exclure, comme ci-dessous :
Fichier : src/lib.rs
pub fn additionne(gauche: u64, droite: u64) -> u64 {
gauche + droite
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cela_fonctionne() {
let resultat = additionne(2, 2);
assert_eq!(resultat, 4);
}
#[test]
#[ignore]
fn test_long() {
// du code qui prend une heure à s'exécuter
}
}
Après #[test], nous avons ajouté la ligne #[ignore] pour le test que nous souhaitons exclure. Maintenant lorsque nous exécutons nos tests, it_works s’exécute, mais pas test_long :
$ cargo test
Compiling addition v0.1.0 (file:///projects/addition)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test test_long ... ignored
test tests::cela_fonctionne ... ok
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests addition
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
La fonction test_long est listée comme ignored. Si nous voulons exécuter uniquement les tests ignorés, nous pouvons utiliser cargo test -- --ignored :
$ cargo test -- --ignored
Compiling addition v0.1.0 (file:///projects/addition)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::test_long ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Doc-tests addition
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
En gérant quels tests sont exécutés, vous pouvez vous assurer que vos résultats de cargo test seront renvoyés rapidement. Lorsque vous arrivez à un stade où il est justifié de vérifier le résultat des tests ignored et que vous avez le temps d’attendre ces résultats, vous pouvez lancer à la place cargo test -- --ignored. Si vous voulez exécuter tous les tests, qu’ils soient ignorés ou non, vous pouvez lancer cargo test -- --include-ignored.