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);
    }
}

Encart 11-10 : tests d'une fonction qui fait appel à 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 [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));
    }
}

Encart 11-11 : trois tests avec trois noms différents

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.