Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Controllare Come Vengono Eseguiti i Test

Così come cargo run compila il tuo codice e poi esegue il binario risultante, cargo test compila il tuo codice in modalità test ed esegue il binario risultante. Il comportamento predefinito del binario prodotto da cargo test è quello di eseguire tutti i test in parallelo e di catturare l’output generato durante l’esecuzione dei test, impedendo la visualizzazione dell’output e rendendo più facile la lettura dell’output relativo ai risultati dei test. Puoi, tuttavia, specificare alcune opzioni della riga di comando per modificare questo comportamento predefinito.

Alcune opzioni della riga di sono per cargo test, mentre altre vengono passate al binario di test risultante.

Per separare questi due tipi di argomenti, devi elencare gli argomenti che vanno a cargo test seguiti dal separatore -- e poi quelli che vanno al binario di test. Eseguendo cargo test --help vengono visualizzate le opzioni che puoi usare con cargo test, mentre eseguendo cargo test -- --help vengono visualizzate le opzioni che puoi usare dopo il separatore. Queste opzioni sono documentate anche nella sezione “Tests” del libro di rustc.

Eseguire i Test in Parallelo o Sequenzialmente

Quando esegui più test, come impostazione predefinita questi vengono eseguiti in parallelo utilizzando i thread, il che significa che finiscono di essere eseguiti più velocemente e che ricevi più rapidamente un feedback. Poiché i test vengono eseguiti contemporaneamente, devi assicurarti che i tuoi test non dipendano l’uno dall’altro o da un qualsivoglia stato condiviso, incluso un ambiente condiviso, come la directory di lavoro corrente o le variabili d’ambiente.

Ad esempio, supponiamo che ogni test esegua del codice che crea un file su disco chiamato test-output.txt e scrive alcuni dati in quel file. Poi ogni test legge i dati in quel file e verifica che il file contiene un particolare valore, che è diverso in ogni test. Poiché i test vengono eseguiti contemporaneamente, un test potrebbe sovrascrivere il file nel tempo che intercorre tra la scrittura e la lettura del file da parte di un altro test. Il secondo test fallirà, non perché il codice non è corretto, ma perché i test hanno interferito l’uno con l’altro durante l’esecuzione in parallelo. Una soluzione può essere nell’assicurarsi che ogni test scriva in un file diverso; un’altra soluzione consiste nell’eseguire i test uno alla volta.

Se non vuoi eseguire i test in parallelo o se vuoi un controllo più preciso sul numero di thread utilizzati, puoi usare il flag --test-threads e il numero di thread che vuoi utilizzare al binario di test. Guarda il seguente esempio:

$ cargo test -- --test-threads=1

Impostiamo il numero di thread di test a 1, indicando al programma di non utilizzare alcun parallelismo. L’esecuzione dei test con un solo thread richiederà più tempo rispetto all’esecuzione in parallelo, ma i test non interferiranno l’uno con l’altro se condividono lo stato.

Mostrare l’Output Della Funzione

Per impostazione predefinita, se un test viene superato, la libreria di test di Rust cattura tutto ciò che viene stampato sullo standard output. Ad esempio, se chiamiamo println! in un test e il test viene superato, non vedremo l’output println! nel terminale; vedremo solo la riga che indica che il test è stato superato. Se un test fallisce, vedremo tutto ciò che è stato stampato sullo standard output con il resto del messaggio di fallimento.

Come esempio, il Listato 11-10 contiene una funzione stupida che stampa il valore del suo parametro e restituisce 10, oltre a un test che passa e uno che fallisce.

File: src/lib.rs
fn stampa_e_ritorna_10(a: i32) -> i32 {
    println!("Ho ricevuto il valore {a}");
    10
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn questo_test_passerà() {
        let valore = stampa_e_ritorna_10(4);
        assert_eq!(valore, 10);
    }

    #[test]
    fn questo_test_fallirà() {
        let valore = stampa_e_ritorna_10(8);
        assert_eq!(valore, 5);
    }
}
Listato 11-10: Test per una funzione che chiama println!

Quando eseguiamo i test con cargo test, vedremo il seguente output:

$ cargo test
   Compiling funzione-stupida v0.1.0 (file:///progetti/funzione-stupida)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.67s
     Running unittests src/lib.rs (target/debug/deps/funzione_stupida-5473422b7951e476)

running 2 tests
test tests::questo_test_passerà ... ok
test tests::questo_test_fallirà ... FAILED

failures:

---- tests::questo_test_fallirà stdout ----
Ho ricevuto il valore 8

thread 'tests::questo_test_fallirà' (8784) 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::questo_test_fallirà

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`

Nota che in nessun punto di questo output vediamo Ho ricevuto il valore 4, che viene stampato quando viene eseguito il test che passa. Quell’output è stato catturato. L’output del test che è fallito, Ho ricevuto il valore 8, appare nella sezione dell’output di riepilogo del test, che mostra anche la causa del fallimento del test.

Se vogliamo vedere anche i valori stampati per i test superati, possiamo dire a Rust di mostrare anche l’output dei test riusciti con --show-output:

$ cargo test -- --show-output

Quando eseguiamo nuovamente i test del Listato 11-10 con il flag --show-output, vediamo il seguente output:

$ cargo test -- --show-output
   Compiling funzione-stupida v0.1.0 (file:///progetti/funzione-stupida)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
     Running unittests src/lib.rs (target/debug/deps/funzione_stupida-5473422b7951e476)

running 2 tests
test tests::questo_test_fallirà ... FAILED
test tests::questo_test_passerà ... ok

successes:

---- tests::questo_test_passerà stdout ----
Ho ricevuto il valore 4


successes:
    tests::questo_test_passerà

failures:

---- tests::questo_test_fallirà stdout ----
Ho ricevuto il valore 8

thread 'tests::questo_test_fallirà' (9352) 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::questo_test_fallirà

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`

Eseguire un Sottoinsieme di Test in Base al Nome

L’esecuzione di tutti i test che abbiamo definito a volte può richiedere molto tempo. Se stai lavorando sul codice di una particolare area, potresti voler eseguire solo i test relativi a quel codice. Puoi scegliere quali test eseguire passando a cargo test il nome o i nomi dei test che vuoi eseguire come argomento.

Per dimostrare come eseguire un sottoinsieme di test, creeremo prima tre test per la nostra funzione aggiungi_due, come mostrato nel Listato 11-11, e sceglieremo quali eseguire.

File: src/lib.rs
pub fn aggiungi_due(a: u64) -> u64 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn somma_due_e_due() {
        let risultato = aggiungi_due(2);
        assert_eq!(risultato, 4);
    }

    #[test]
    fn somma_due_e_tre() {
        let risultato = aggiungi_due(3);
        assert_eq!(risultato, 5);
    }

    #[test]
    fn cento() {
        let risultato = aggiungi_due(100);
        assert_eq!(risultato, 102);
    }
}
Listato 11-11: Tre test con tre nomi diversi

Se eseguiamo i test senza passare alcun argomento, come abbiamo visto in precedenza, tutti i test verranno eseguiti in parallelo:

$ cargo test
   Compiling addizione v0.1.0 (file:///progetti/addizione)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
     Running unittests src/lib.rs (target/debug/deps/addizione-41054392f08da196)

running 3 tests
test tests::cento ... ok
test tests::somma_due_e_tre ... ok
test tests::somma_due_e_due ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests addizione

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Eseguire un Singolo Test

Possiamo passare il nome di qualsiasi funzione di test a cargo test per eseguire solo quel test:

$ cargo test cento
   Compiling addizione v0.1.0 (file:///progetti/addizione)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.81s
     Running unittests src/lib.rs (target/debug/deps/addizione-41054392f08da196)

running 1 test
test tests::cento ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

Solo il test con il nome cento è stato eseguito; gli altri due test non corrispondevano a quel nome. L’output del test ci fa sapere che ci sono altri test che non sono stati eseguiti mostrando 2 filtered out alla fine.

Non possiamo specificare più di un nome in questo modo; verrà utilizzato solo il primo valore dato a cargo test. Esiste però un modo per eseguire più test.

Filtrare Per Eseguire Più Test

Possiamo specificare una parte del nome di un test e ogni test il cui nome corrisponde a quel valore verrà eseguito. Ad esempio, poiché due dei nomi dei nostri test contengono somma, possiamo eseguirli eseguendo cargo test somma:

$ cargo test somma
   Compiling addizione v0.1.0 (file:///progetti/addizione)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.11s
     Running unittests src/lib.rs (target/debug/deps/addizione-41054392f08da196)

running 2 tests
test tests::somma_due_e_tre ... ok
test tests::somma_due_e_due ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

Questo comando ha eseguito tutti i test con somma nel nome e ha filtrato il test chiamato cento. Nota anche che il modulo in cui appare un test diventa parte del nome del test, quindi possiamo eseguire tutti i test di un modulo filtrando sul nome del modulo.

Ignorare Test Se Non Specificamente Richiesti

A volte alcuni test specifici possono richiedere molto tempo per essere eseguiti, quindi potresti volerli escludere durante la maggior parte delle esecuzioni di cargo test. Invece di elencare come argomenti tutti i test che vuoi eseguire, puoi annotare i test che richiedono molto tempo utilizzando l’attributo ignore per escluderli, come mostrato qui:

File: src/lib.rs

pub fn aggiungi_due(a: u64) -> u64 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn somma_due_e_due() {
        let risultato = aggiungi_due(2);
        assert_eq!(risultato, 4);
    }

    #[test]
    #[ignore]
    fn test_impegnativo() {
        // codice che richiede un'ora per completarsi
    }
}

Dopo #[test], aggiungiamo la riga #[ignore] al test che vogliamo escludere. Ora quando eseguiamo i nostri test, somma_due_e_due viene eseguito, ma test_impegnativo no:

$ cargo test
   Compiling addizione v0.1.0 (file:///progetti/addizione)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.32s
     Running unittests src/lib.rs (target/debug/deps/addizione-41054392f08da196)

running 2 tests
test tests::test_impegnativo ... ignored
test tests::somma_due_e_due ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests addizione

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

La funzione test_impegnativo è elencata come ignored. Se vogliamo eseguire solo i test ignorati, possiamo usare cargo test -- --ignored:

$ cargo test -- --ignored
   Compiling addizione v0.1.0 (file:///progetti/addizione)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.32s
     Running unittests src/lib.rs (target/debug/deps/addizione-41054392f08da196)

running 1 test
test tests::test_impegnativo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

   Doc-tests addizione

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Controllando quali test vengono eseguiti, puoi assicurarti che i risultati di cargo test vengano restituiti rapidamente. Quando ha senso controllare i risultati dei test ignored e hai il tempo di aspettare i risultati, puoi invece eseguire cargo test -- --ignored. Se vuoi eseguire tutti i test, indipendentemente dal fatto che siano ignorati o meno, puoi eseguire cargo test -- --include-ignored