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