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. Vous pouvez rajouter des options en ligne de commande pour
changer le comportement par défaut de cargo test
. Par exemple, 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.
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 --
.
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. Cela veut dire que tous les tests vont finir de s'exécuter plus rapidement afin que vous sachiez si votre code fonctionne ou non. Comme les tests s'exécutent en même temps, il faut s'assurer qu'ils ne dépendent pas les uns des autres ou d'un état partagé, y compris un environnement partagé, comme le dossier 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.
Fichier : src/lib.rs
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!(10, valeur);
}
#[test]
fn ce_test_echoue() {
let valeur = affiche_et_retourne_10(8);
assert_eq!(5, valeur);
}
}
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 [unoptimized + debuginfo] target(s) in 0.58s
Running unittests (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 '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::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 ce qui est affiché 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 à la fin --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 [unoptimized + debuginfo] target(s) in 0.60s
Running unittests (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 '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::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
Parfois, lancer une suite de tests entière peut 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
créer trois tests pour notre fonction ajouter_deux
dans l'encart 11-11, et
choisir lesquels nous allons exécuter.
Fichier : src/lib.rs
pub fn ajouter_deux(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ajouter_deux_a_deux() {
assert_eq!(4, ajouter_deux(2));
}
#[test]
fn ajouter_deux_a_trois() {
assert_eq!(5, ajouter_deux(3));
}
#[test]
fn cent() {
assert_eq!(102, ajouter_deux(100));
}
}
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 adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests (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 adder
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 adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.69s
Running unittests (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 en plus de celui que cette commande a exécuté 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 adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (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 certains 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
#[test]
fn it_works() {
assert_eq!(2 + 2, 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 adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.60s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test test_long ... ignored
test it_works ... ok
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
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 adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test test_long ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Doc-tests adder
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 rapides. 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 q'uils soient
ignorés ou non, vous pouvez lancer cargo test -- --include-ignored
.