Controlando a Execução de Testes

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Bem-vindo a Controlando Como os Testes são Executados. Este laboratório faz parte do Livro Rust. Você pode praticar suas habilidades em Rust no LabEx.

Neste laboratório, você aprenderá como controlar o comportamento das execuções de testes em Rust usando opções de linha de comando para cargo test e o binário de teste resultante.

Controlando Como os Testes são Executados

Assim como cargo run compila seu código e então executa o binário resultante, cargo test compila seu código em modo de teste e executa o binário de teste 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 dos testes, impedindo que a saída seja exibida e tornando mais fácil ler a saída relacionada aos resultados dos testes. Você pode, no entanto, especificar opções de linha de comando para alterar este comportamento padrão.

Algumas opções de linha de comando vão para cargo test, e algumas vão para o binário de teste resultante. Para separar esses dois tipos de argumentos, você lista os argumentos que vão para cargo test seguidos pelo separador -- e, em seguida, aqueles que vão para o 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 após o separador.

Executando Testes em Paralelo ou Consecutivamente

Quando você executa vários testes, por padrão eles são executados em paralelo usando threads, o que significa que eles terminam de rodar mais rápido e você recebe feedback mais rapidamente. Como os testes estão sendo executados ao mesmo tempo, você deve garantir que seus testes não dependam uns dos outros ou de qualquer estado compartilhado, incluindo um ambiente compartilhado, como o diretório de trabalho atual ou variáveis de ambiente.

Por exemplo, digamos que cada um de seus testes execute algum código que cria um arquivo no disco chamado test-output.txt e grava alguns dados nesse arquivo. Então, cada teste lê os dados nesse arquivo e afirma que o arquivo contém um valor específico, que é diferente em cada teste. Como os testes são executados ao mesmo tempo, um teste pode sobrescrever o arquivo no tempo entre outro teste escrever e ler o arquivo. O segundo teste falhará, não porque o código está incorreto, mas porque os testes interferiram uns com os outros durante a execução em paralelo. Uma solução é garantir que cada teste grave em um arquivo diferente; outra solução é executar os testes um de cada vez.

Se você não quiser executar os testes em paralelo ou se quiser um controle mais preciso sobre o número de threads usados, você pode enviar a flag --test-threads e o número de threads que deseja usar para o binário de teste. Dê uma olhada no exemplo a seguir:

cargo test -- --test-threads=1

Definimos o número de threads de teste para 1, dizendo ao programa para não usar nenhum paralelismo. Executar os testes usando uma thread levará mais tempo do que executá-los em paralelo, mas os testes não interferirão uns com os outros se compartilharem estado.

Mostrando a Saída da Função

Por padrão, se um teste passa, a biblioteca de testes do Rust captura tudo o que é impresso na saída padrão. Por exemplo, se chamarmos println! em um teste e o 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 com o restante da mensagem de falha.

Como exemplo, a Listagem 11-10 tem uma função boba que imprime o valor de seu parâmetro e retorna 10, bem como um teste que passa e um teste que falha.

Nome do arquivo: 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!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

Listagem 11-10: Testes para uma função que chama println!

Quando executamos esses testes com cargo test, veremos a seguinte saída:

running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED

failures:

---- tests::this_test_will_fail stdout ----
1 I got the value 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::this_test_will_fail

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

Observe que em nenhum lugar nesta saída vemos I got the value 4, que é impresso quando o teste que passa é executado. Essa saída foi capturada. A saída do teste que falhou, I got the value 8 [1], aparece na seção da saída do resumo do teste, que também mostra a causa da falha do teste.

Se quisermos ver os valores impressos também para os testes que passam, podemos dizer ao Rust para também mostrar a saída dos testes bem-sucedidos com --show-output:

cargo test -- --show-output

Quando executamos os testes na Listagem 11-10 novamente com a flag --show-output, vemos a seguinte saída:

running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED

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 '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::this_test_will_fail

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

Executando um Subconjunto de Testes por Nome

Às vezes, executar um conjunto completo de testes pode levar muito tempo. Se você estiver trabalhando em código em uma área específica, pode querer executar apenas os testes relacionados a esse código. Você pode escolher quais testes executar passando para cargo test o nome ou nomes do(s) teste(s) que deseja executar como um argumento.

Para demonstrar como executar um subconjunto de testes, primeiro criaremos três testes para nossa função add_two, conforme mostrado na Listagem 11-11, e escolheremos quais executar.

Nome do arquivo: src/lib.rs

pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

Listagem 11-11: Três testes com três nomes diferentes

Se executarmos os testes sem passar nenhum argumento, como vimos anteriormente, todos os testes serão executados em paralelo:

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

Executando Testes Únicos

Podemos passar o nome de qualquer função de teste para cargo test para executar apenas esse teste:

[object Object]

Apenas o teste com o nome one_hundred foi executado; os outros dois testes não corresponderam a esse nome. A saída do teste nos informa que tínhamos mais testes que não foram executados, exibindo 2 filtered out no final.

Não podemos especificar os nomes de vários testes dessa forma; apenas o primeiro valor fornecido para cargo test será usado. Mas existe uma maneira de executar vários testes.

Filtrando para Executar Vários Testes

Podemos especificar parte de um nome de teste, e qualquer teste cujo nome corresponda a esse valor será executado. Por exemplo, como dois dos nomes de nossos testes contêm add, podemos executar esses dois executando cargo test add:

[object Object]

Este comando executou todos os testes com add no nome e filtrou o teste chamado one_hundred. Observe também que o módulo no qual um teste aparece se torna parte do nome do teste, então podemos executar todos os testes em um módulo filtrando pelo nome do módulo.

Ignorando Alguns Testes, a Menos que Especificamente Solicitado

Às vezes, alguns testes específicos podem ser muito demorados para executar, então você pode querer excluí-los durante a maioria das execuções de cargo test. Em vez de listar como argumentos todos os testes que você deseja executar, você pode, em vez disso, anotar os testes demorados usando o atributo ignore para excluí-los, conforme mostrado aqui:

Nome do arquivo: src/lib.rs

#[test]
fn it_works() {
    let result = 2 + 2;
    assert_eq!(result, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // código que leva uma hora para ser executado
}

Após #[test], adicionamos a linha #[ignore] ao teste que queremos excluir. Agora, quando executamos nossos testes, it_works é executado, mas expensive_test não:

[object Object]

A função expensive_test é listada como ignored (ignorada). Se quisermos executar apenas os testes ignorados, podemos usar cargo test -- --ignored:

[object Object]

Ao controlar quais testes são executados, você pode garantir que seus resultados cargo test sejam retornados rapidamente. Quando você estiver em um ponto em que faz sentido verificar os resultados dos testes ignored e tiver tempo para esperar pelos resultados, você pode executar cargo test -- --ignored em vez disso. Se você quiser executar todos os testes, sejam eles ignorados ou não, você pode executar cargo test -- --include-ignored.

Resumo

Parabéns! Você concluiu o laboratório Controlando Como os Testes São Executados. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.