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

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.

Filename: src/lib.rs
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);
    }
}
Listing 11-10: Testes para uma função que chama 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.

Filename: src/lib.rs
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);
    }
}
Listing 11-11: Três testes com três nomes diferentes

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.