Controlando como os testes são executados
Assim como cargo run compila seu código e depois executa o binário
resultante, cargo test compila seu código em modo de teste e executa o
binário de testes resultante. O comportamento padrão do binário produzido por
cargo test é executar todos os testes em paralelo e capturar a saída gerada
durante as execuções, impedindo que ela seja exibida e facilitando a leitura da
saída relacionada aos resultados. Você pode, no entanto, especificar opções de
linha de comando para alterar esse comportamento padrão.
Algumas opções de linha de comando são passadas para cargo test, e outras são
passadas para o binário de teste resultante. Para separar esses dois tipos de
argumento, liste os argumentos destinados a cargo test, depois o separador
--, e então os argumentos destinados ao binário de teste. Executar
cargo test --help exibe as opções que você pode usar com cargo test, e
executar cargo test -- --help exibe as opções que você pode usar depois do
separador. Essas opções também estão documentadas na seção “Tests” de
The rustc Book.
Executando testes em paralelo ou em sequência
Quando você executa vários testes, por padrão eles rodam em paralelo usando threads, o que significa que terminam mais rápido e você recebe feedback mais cedo. Como os testes estão rodando ao mesmo tempo, é preciso garantir que eles não dependam uns dos outros nem de algum estado compartilhado, incluindo um ambiente compartilhado, como o diretório de trabalho atual ou variáveis de ambiente.
Por exemplo, imagine que cada teste execute código que cria um arquivo em disco chamado test-output.txt e escreva alguns dados nele. Em seguida, cada teste lê os dados desse arquivo e verifica que ele contém um determinado valor, diferente em cada teste. Como os testes são executados ao mesmo tempo, um teste pode sobrescrever o arquivo no intervalo entre outro teste escrevê-lo e lê-lo. O segundo teste então falhará, não porque o código esteja incorreto, mas porque os testes interferiram uns nos outros durante a execução em paralelo. Uma solução é garantir que cada teste escreva em um arquivo diferente; outra é executar os testes um de cada vez.
Se você não quiser executar os testes em paralelo, ou se quiser um controle
mais detalhado sobre o número de threads usadas, pode passar a flag
--test-threads e a quantidade de threads desejada ao binário de teste. Veja o
seguinte exemplo:
$ cargo test -- --test-threads=1
Definimos o número de threads de teste como 1, instruindo o programa a não
usar paralelismo. Executar os testes usando apenas uma thread levará mais tempo
do que executá-los em paralelo, mas os testes não interferirão entre si caso
compartilhem estado.
Mostrando a saída de funções
Por padrão, se um teste passa, a biblioteca de testes do Rust captura tudo o
que foi impresso na saída padrão. Por exemplo, se chamarmos println! em um
teste e esse teste passar, não veremos a saída de println! no terminal;
veremos apenas a linha que indica que o teste passou. Se um teste falhar,
veremos o que foi impresso na saída padrão junto com o restante da mensagem de
falha.
Como exemplo, a Listagem 11-10 tem uma função simples que imprime o valor de seu parâmetro e retorna 10, além de um teste que passa e um teste que falha.
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {a}");
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_test_will_pass() {
let value = prints_and_returns_10(4);
assert_eq!(value, 10);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(value, 5);
}
}
println!Quando executarmos esses testes com cargo test, veremos a seguinte saída:
$ 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::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' 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::this_test_will_fail
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`
Observe que em nenhum ponto dessa saída vemos I got the value 4, que é o
texto impresso quando o teste que passa é executado. Essa saída foi capturada.
A saída do teste que falhou, I got the value 8, aparece na seção do resumo de
testes, que também mostra a causa da falha.
Se quisermos ver os valores impressos também para testes que passam, podemos
pedir ao Rust para mostrar a saída de testes bem-sucedidos com
--show-output:
$ cargo test -- --show-output
Quando executarmos novamente os testes da Listagem 11-10 com a flag
--show-output, veremos a seguinte saída:
$ 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::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok
successes:
---- tests::this_test_will_pass stdout ----
I got the value 4
successes:
tests::this_test_will_pass
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' 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::this_test_will_fail
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`
Executando um subconjunto de testes por nome
Executar uma suíte completa de testes às vezes pode levar bastante tempo. Se
você estiver trabalhando em código de uma área específica, talvez queira
executar apenas os testes relacionados àquela parte. Você pode escolher quais
testes rodar passando ao cargo test o nome, ou os nomes, do(s) teste(s) que
quer executar como argumento.
Para demonstrar como executar um subconjunto de testes, primeiro criaremos três
testes para nossa função add_two, como mostra a Listagem 11-11, e veremos
quais deles são executados.
pub fn add_two(a: u64) -> u64 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
let result = add_two(2);
assert_eq!(result, 4);
}
#[test]
fn add_three_and_two() {
let result = add_two(3);
assert_eq!(result, 5);
}
#[test]
fn one_hundred() {
let result = add_two(100);
assert_eq!(result, 102);
}
}
Se executarmos os testes sem passar nenhum argumento, como vimos antes, todos os testes serão executados em paralelo:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
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::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... 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
Executando testes individuais
Podemos passar o nome de qualquer função de teste a cargo test para executar
somente aquele teste:
$ cargo test one_hundred
Compiling adder v0.1.0 (file:///projects/adder)
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::one_hundred ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
Apenas o teste chamado one_hundred foi executado; os outros dois não
corresponderam a esse nome. A saída nos informa que havia mais testes não
executados exibindo 2 filtered out no final.
Não podemos especificar os nomes de vários testes dessa forma; somente o
primeiro valor fornecido a cargo test será usado. Mas existe uma forma de
executar vários testes.
Filtrando para executar vários testes
Podemos especificar parte do nome de um teste, e qualquer teste cujo nome
corresponda a esse valor será executado. Por exemplo, como os nomes de dois dos
nossos testes contêm add, podemos executá-los rodando cargo test add:
$ cargo test add
Compiling adder v0.1.0 (file:///projects/adder)
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::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
Esse comando executou todos os testes cujo nome contém add e filtrou o teste
chamado one_hundred. Observe também que o módulo no qual um teste aparece
passa a fazer parte do nome do teste, de modo que podemos executar todos os
testes de um módulo filtrando pelo nome desse módulo.
Ignorando testes, a menos que sejam solicitados especificamente
Às vezes, alguns testes específicos podem levar muito tempo para executar, então
talvez você queira excluí-los da maioria das execuções de cargo test. Em vez
de listar como argumentos todos os testes que deseja executar, você pode
anotar os testes demorados com o atributo ignore para excluí-los, como
mostrado aqui:
Nome do arquivo: src/lib.rs
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
}
Depois de #[test], adicionamos a linha #[ignore] ao teste que queremos
excluir. Agora, quando executamos os testes, it_works roda, mas
expensive_test não:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::expensive_test ... ignored
test tests::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
A função expensive_test aparece listada como ignored. Se quisermos executar
somente os testes ignorados, podemos usar cargo test -- --ignored:
$ cargo test -- --ignored
Compiling adder v0.1.0 (file:///projects/adder)
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::expensive_test ... 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
Ao controlar quais testes são executados, você pode garantir que os resultados
de cargo test retornem rapidamente. Quando chegar a um ponto em que faça
sentido verificar os resultados dos testes ignored e você tiver tempo para
esperar, poderá executar cargo test -- --ignored. Se quiser rodar todos os
testes, ignorados ou não, pode executar cargo test -- --include-ignored.