Como Escrever Testes

Beginner

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

Introdução

Bem-vindo a Como Escrever Testes. Este laboratório faz parte do Livro Rust. Você pode praticar suas habilidades em Rust no LabEx.

Neste laboratório, aprenderemos sobre como escrever testes em Rust usando atributos, macros e asserções (assertions).

Como Escrever Testes

Testes são funções Rust que verificam se o código que não é de teste está funcionando da maneira esperada. Os corpos das funções de teste normalmente executam estas três ações:

  • Configurar quaisquer dados ou estado necessários.
  • Executar o código que você deseja testar.
  • Afirmar (Assert) que os resultados são o que você espera.

Vamos analisar os recursos que o Rust fornece especificamente para escrever testes que executam essas ações, que incluem o atributo test, algumas macros e o atributo should_panic.

A Anatomia de uma Função de Teste

Em sua forma mais simples, um teste em Rust é uma função que é anotada com o atributo test. Atributos são metadados sobre partes do código Rust; um exemplo é o atributo derive que usamos com structs no Capítulo 5. Para transformar uma função em uma função de teste, adicione #[test] na linha antes de fn. Quando você executa seus testes com o comando cargo test, o Rust constrói um binário de execução de testes que executa as funções anotadas e relata se cada função de teste passa ou falha.

Sempre que criamos um novo projeto de biblioteca com o Cargo, um módulo de teste com uma função de teste é gerado automaticamente para nós. Este módulo fornece um modelo para escrever seus testes, para que você não precise procurar a estrutura e a sintaxe exatas toda vez que iniciar um novo projeto. Você pode adicionar quantas funções de teste adicionais e quantos módulos de teste quiser!

Vamos explorar alguns aspectos de como os testes funcionam, experimentando o teste de modelo antes de realmente testar qualquer código. Em seguida, escreveremos alguns testes do mundo real que chamam algum código que escrevemos e afirmam que seu comportamento está correto.

Vamos criar um novo projeto de biblioteca chamado adder que irá somar dois números:

$ cargo new adder --lib
Created library $(adder) project
$ cd adder

O conteúdo do arquivo src/lib.rs em sua biblioteca adder deve ser semelhante ao da Listagem 11-1.

Nome do arquivo: src/lib.rs

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

Listagem 11-1: O módulo de teste e a função gerados automaticamente por cargo new

Por enquanto, vamos ignorar as duas primeiras linhas e focar na função. Observe a anotação #[test] [1]: este atributo indica que esta é uma função de teste, então o executor de testes sabe que deve tratar esta função como um teste. Também podemos ter funções que não são de teste no módulo tests para ajudar a configurar cenários comuns ou realizar operações comuns, então sempre precisamos indicar quais funções são testes.

O corpo da função de exemplo usa a macro assert_eq! [2] para afirmar que result, que contém o resultado da soma de 2 e 2, é igual a 4. Essa asserção serve como um exemplo do formato para um teste típico. Vamos executá-lo para ver que este teste passa.

O comando cargo test executa todos os testes em nosso projeto, conforme mostrado na Listagem 11-2.

[object Object]

Listagem 11-2: A saída da execução do teste gerado automaticamente

O Cargo compilou e executou o teste. Vemos a linha running 1 test [1]. A próxima linha mostra o nome da função de teste gerada, chamada it_works, e que o resultado da execução desse teste é ok [2]. O resumo geral test result: ok. [3] significa que todos os testes passaram, e a parte que diz 1 passed; 0 failed totaliza o número de testes que passaram ou falharam.

É possível marcar um teste como ignorado para que ele não seja executado em uma instância específica; abordaremos isso em "Ignorando Alguns Testes, a Menos que Especificamente Solicitado". Como não fizemos isso aqui, o resumo mostra 0 ignored. Também podemos passar um argumento para o comando cargo test para executar apenas testes cujo nome corresponda a uma string; isso é chamado de filtragem e abordaremos isso em "Executando um Subconjunto de Testes por Nome". Aqui, não filtramos os testes que estão sendo executados, então o final do resumo mostra 0 filtered out.

A estatística 0 measured é para testes de benchmark que medem o desempenho. Testes de benchmark estão, no momento em que este texto é escrito, disponíveis apenas no Rust nightly. Consulte a documentação sobre testes de benchmark em https://doc.rust-lang.org/unstable-book/library-features/test.html para saber mais.

A próxima parte da saída do teste, começando em Doc-tests adder [4], é para os resultados de quaisquer testes de documentação. Ainda não temos nenhum teste de documentação, mas o Rust pode compilar quaisquer exemplos de código que apareçam em nossa documentação da API. Esse recurso ajuda a manter seus documentos e seu código sincronizados! Discutiremos como escrever testes de documentação em "Comentários de Documentação como Testes". Por enquanto, ignoraremos a saída Doc-tests.

Vamos começar a personalizar o teste para nossas próprias necessidades. Primeiro, altere o nome da função it_works para um nome diferente, como exploration, assim:

Nome do arquivo: src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn exploration() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

Em seguida, execute cargo test novamente. A saída agora mostra exploration em vez de it_works:

running 1 test
test tests::exploration ... ok

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

Agora, adicionaremos outro teste, mas desta vez faremos um teste que falha! Os testes falham quando algo na função de teste entra em pânico. Cada teste é executado em um novo thread, e quando o thread principal vê que um thread de teste morreu, o teste é marcado como falhado. No Capítulo 9, falamos sobre como a maneira mais simples de entrar em pânico é chamar a macro panic!. Insira o novo teste como uma função chamada another, para que seu arquivo src/lib.rs fique como a Listagem 11-3.

Nome do arquivo: src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn exploration() {
        assert_eq!(2 + 2, 4);
    }

    #[test]
    fn another() {
        panic!("Make this test fail");
    }
}

Listagem 11-3: Adicionando um segundo teste que falhará porque chamamos a macro panic!

Execute os testes novamente usando cargo test. A saída deve ser semelhante à Listagem 11-4, que mostra que nosso teste exploration passou e another falhou.

running 2 tests
test tests::exploration ... ok
1 test tests::another ... FAILED

2 failures:

---- tests::another stdout ----
thread 'main' panicked at 'Make this test fail', src/lib.rs:10:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace

3 failures:
    tests::another

4 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'

Listagem 11-4: Resultados do teste quando um teste passa e um teste falha

Em vez de ok, a linha test tests::another mostra FAILED [1]. Duas novas seções aparecem entre os resultados individuais e o resumo: a primeira [2] exibe o motivo detalhado de cada falha no teste. Neste caso, obtemos os detalhes de que another falhou porque entrou em pânico em 'Make this test fail' na linha 10 no arquivo src/lib.rs. A próxima seção [3] lista apenas os nomes de todos os testes com falha, o que é útil quando há muitos testes e muita saída detalhada de testes com falha. Podemos usar o nome de um teste com falha para executar apenas esse teste para depurá-lo mais facilmente; falaremos mais sobre maneiras de executar testes em "Controlando Como os Testes São Executados".

A linha de resumo é exibida no final [4]: no geral, nosso resultado do teste é FAILED. Tivemos um teste que passou e um teste que falhou.

Agora que você viu como os resultados do teste se parecem em diferentes cenários, vamos analisar algumas macros diferentes de panic! que são úteis em testes.

Verificando Resultados com a Macro assert!

A macro assert!, fornecida pela biblioteca padrão, é útil quando você deseja garantir que alguma condição em um teste seja avaliada como true. Damos à macro assert! um argumento que é avaliado como um booleano. Se o valor for true, nada acontece e o teste passa. Se o valor for false, a macro assert! chama panic! para fazer com que o teste falhe. Usar a macro assert! nos ajuda a verificar se nosso código está funcionando da maneira que pretendemos.

Na Listagem 5-15, usamos uma struct Rectangle e um método can_hold, que são repetidos aqui na Listagem 11-5. Vamos colocar este código no arquivo src/lib.rs e, em seguida, escrever alguns testes para ele usando a macro assert!.

Nome do arquivo: src/lib.rs

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

Listagem 11-5: Usando a struct Rectangle e seu método can_hold do Capítulo 5

O método can_hold retorna um booleano, o que significa que é um caso de uso perfeito para a macro assert!. Na Listagem 11-6, escrevemos um teste que exercita o método can_hold criando uma instância Rectangle que tem uma largura de 8 e uma altura de 7 e afirmando que ela pode conter outra instância Rectangle que tem uma largura de 5 e uma altura de 1.

Nome do arquivo: src/lib.rs

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

    #[test]
  2 fn larger_can_hold_smaller() {
      3 let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

      4 assert!(larger.can_hold(&smaller));
    }
}

Listagem 11-6: Um teste para can_hold que verifica se um retângulo maior pode realmente conter um retângulo menor

Observe que adicionamos uma nova linha dentro do módulo tests: use super::*; [1]. O módulo tests é um módulo regular que segue as regras de visibilidade usuais que abordamos em "Caminhos para se Referir a um Item na Árvore de Módulos". Como o módulo tests é um módulo interno, precisamos trazer o código sob teste no módulo externo para o escopo do módulo interno. Usamos um glob aqui, então tudo o que definimos no módulo externo está disponível para este módulo tests.

Nomeamos nosso teste larger_can_hold_smaller [2] e criamos as duas instâncias Rectangle de que precisamos [3]. Em seguida, chamamos a macro assert! e passamos a ela o resultado da chamada larger.can_hold(&smaller) [4]. Espera-se que esta expressão retorne true, então nosso teste deve passar. Vamos descobrir!

running 1 test
test tests::larger_can_hold_smaller ... ok

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

Ele passa! Vamos adicionar outro teste, desta vez afirmando que um retângulo menor não pode conter um retângulo maior:

Nome do arquivo: src/lib.rs

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

    #[test]
    fn larger_can_hold_smaller() {
        --snip--
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(!smaller.can_hold(&larger));
    }
}

Como o resultado correto da função can_hold neste caso é false, precisamos negar esse resultado antes de passá-lo para a macro assert!. Como resultado, nosso teste passará se can_hold retornar false:

running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok

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

Dois testes que passam! Agora vamos ver o que acontece com os resultados do nosso teste quando introduzimos um bug em nosso código. Vamos alterar a implementação do método can_hold substituindo o sinal de maior que por um sinal de menor que ao comparar as larguras:

--snip--

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width < other.width && self.height > other.height
    }
}

A execução dos testes agora produz o seguinte:

running 2 tests
test tests::smaller_cannot_hold_larger ... ok
test tests::larger_can_hold_smaller ... FAILED

failures:

---- tests::larger_can_hold_smaller stdout ----
thread 'main' panicked at 'assertion failed:
larger.can_hold(&smaller)', src/lib.rs:28:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace


failures:
    tests::larger_can_hold_smaller

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

Nossos testes detectaram o bug! Como larger.width é 8 e smaller.width é 5, a comparação das larguras em can_hold agora retorna false: 8 não é menor que 5.

Testando a Igualdade com as Macros assert_eq! e assert_ne!

Uma forma comum de verificar a funcionalidade é testar a igualdade entre o resultado do código sob teste e o valor que você espera que o código retorne. Você pode fazer isso usando a macro assert! e passando a ela uma expressão usando o operador ==. No entanto, este é um teste tão comum que a biblioteca padrão fornece um par de macros --- assert_eq! e assert_ne! --- para realizar este teste de forma mais conveniente. Essas macros comparam dois argumentos quanto à igualdade ou desigualdade, respectivamente. Elas também imprimirão os dois valores se a asserção falhar, o que torna mais fácil ver por que o teste falhou; por outro lado, a macro assert! apenas indica que obteve um valor false para a expressão ==, sem imprimir os valores que levaram ao valor false.

Na Listagem 11-7, escrevemos uma função chamada add_two que adiciona 2 ao seu parâmetro e, em seguida, testamos essa função usando a macro assert_eq!.

Nome do arquivo: src/lib.rs

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

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

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

Listagem 11-7: Testando a função add_two usando a macro assert_eq!

Vamos verificar se ela passa!

running 1 test
test tests::it_adds_two ... ok

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

Passamos 4 como argumento para assert_eq!, que é igual ao resultado da chamada add_two(2). A linha para este teste é test tests::it_adds_two ... ok, e o texto ok indica que nosso teste passou!

Vamos introduzir um bug em nosso código para ver como assert_eq! se parece quando falha. Altere a implementação da função add_two para, em vez disso, adicionar 3:

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

Execute os testes novamente:

running 1 test
test tests::it_adds_two ... FAILED

failures:

---- tests::it_adds_two stdout ----
1 thread 'main' panicked at 'assertion failed: `(left == right)`
2   left: `4`,
3  right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace

failures:
    tests::it_adds_two

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

Nosso teste detectou o bug! O teste it_adds_two falhou, e a mensagem nos diz que a asserção que falhou foi assertion failed:(left == right)`[1] e quais são os valoresleft[2] eright[3]. Essa mensagem nos ajuda a começar a depurar: o argumentoleftera4, mas o argumentoright, onde tínhamosadd_two(2), era5`. Você pode imaginar que isso seria especialmente útil quando temos muitos testes em andamento.

Observe que em algumas linguagens e frameworks de teste, os parâmetros para funções de asserção de igualdade são chamados de expected e actual, e a ordem em que especificamos os argumentos importa. No entanto, em Rust, eles são chamados de left e right, e a ordem em que especificamos o valor que esperamos e o valor que o código produz não importa. Poderíamos escrever a asserção neste teste como assert_eq!(add_two(2), 4), o que resultaria na mesma mensagem de falha que exibe assertion failed:(left == right)``.

A macro assert_ne! passará se os dois valores que lhe dermos não forem iguais e falhará se forem iguais. Essa macro é mais útil para casos em que não temos certeza de qual valor será, mas sabemos qual valor definitivamente não deveria ser. Por exemplo, se estamos testando uma função que tem garantia de alterar sua entrada de alguma forma, mas a maneira como a entrada é alterada depende do dia da semana em que executamos nossos testes, a melhor coisa a afirmar pode ser que a saída da função não é igual à entrada.

Por baixo, as macros assert_eq! e assert_ne! usam os operadores == e !=, respectivamente. Quando as asserções falham, essas macros imprimem seus argumentos usando a formatação de depuração, o que significa que os valores que estão sendo comparados devem implementar os traits PartialEq e Debug. Todos os tipos primitivos e a maioria dos tipos da biblioteca padrão implementam esses traits. Para structs e enums que você define por conta própria, você precisará implementar PartialEq para afirmar a igualdade desses tipos. Você também precisará implementar Debug para imprimir os valores quando a asserção falhar. Como ambos os traits são traits deriváveis, conforme mencionado na Listagem 5-12, isso geralmente é tão simples quanto adicionar a anotação #[derive(PartialEq, Debug)] à sua definição de struct ou enum. Consulte o Apêndice C para obter mais detalhes sobre esses e outros traits deriváveis.

Adicionando Mensagens de Falha Personalizadas

Você também pode adicionar uma mensagem personalizada para ser impressa com a mensagem de falha como argumentos opcionais para as macros assert!, assert_eq! e assert_ne!. Quaisquer argumentos especificados após os argumentos obrigatórios são passados para a macro format! (discutida em "Concatenação com o Operador + ou a Macro format!"), para que você possa passar uma string de formatação que contenha espaços reservados {} e valores para preencher esses espaços reservados. Mensagens personalizadas são úteis para documentar o que uma asserção significa; quando um teste falha, você terá uma ideia melhor de qual é o problema com o código.

Por exemplo, digamos que temos uma função que cumprimenta as pessoas pelo nome e queremos testar se o nome que passamos para a função aparece na saída:

Nome do arquivo: src/lib.rs

pub fn greeting(name: &str) -> String {
    format!("Olá {name}!")
}

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

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(result.contains("Carol"));
    }
}

Os requisitos para este programa ainda não foram acordados, e temos certeza de que o texto Olá no início da saudação mudará. Decidimos que não queremos ter que atualizar o teste quando os requisitos mudarem, então, em vez de verificar a igualdade exata com o valor retornado da função greeting, apenas afirmaremos que a saída contém o texto do parâmetro de entrada.

Agora, vamos introduzir um bug neste código alterando greeting para excluir name para ver como a falha de teste padrão se parece:

pub fn greeting(name: &str) -> String {
    String::from("Olá!")
}

A execução deste teste produz o seguinte:

running 1 test
test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----
thread 'main' panicked at 'assertion failed:
result.contains(\"Carol\")', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace


failures:
    tests::greeting_contains_name

Este resultado apenas indica que a asserção falhou e em qual linha a asserção está. Uma mensagem de falha mais útil imprimiria o valor da função greeting. Vamos adicionar uma mensagem de falha personalizada composta por uma string de formatação com um espaço reservado preenchido com o valor real que obtivemos da função greeting:

#[test]
fn greeting_contains_name() {
    let result = greeting("Carol");
    assert!(
        result.contains("Carol"),
        "A saudação não continha o nome, o valor era `{result}`"
    );
}

Agora, quando executarmos o teste, obteremos uma mensagem de erro mais informativa:

---- tests::greeting_contains_name stdout ----
thread 'main' panicked at 'A saudação não continha o nome, o valor
era `Olá!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace

Podemos ver o valor que realmente obtivemos na saída do teste, o que nos ajudaria a depurar o que aconteceu em vez do que estávamos esperando que acontecesse.

Verificando Panics com should_panic

Além de verificar os valores de retorno, é importante verificar se nosso código lida com as condições de erro como esperamos. Por exemplo, considere o tipo Guess que criamos na Listagem 9-13. Outro código que usa Guess depende da garantia de que as instâncias de Guess conterão apenas valores entre 1 e 100. Podemos escrever um teste que garante que tentar criar uma instância de Guess com um valor fora dessa faixa cause um pânico.

Fazemos isso adicionando o atributo should_panic à nossa função de teste. O teste passa se o código dentro da função entrar em pânico; o teste falha se o código dentro da função não entrar em pânico.

A Listagem 11-8 mostra um teste que verifica se as condições de erro de Guess::new acontecem quando esperamos que aconteçam.

// src/lib.rs
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!(
                "O valor de Guess deve estar entre 1 e 100, obteve {}.",
                value
            );
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

Listagem 11-8: Testando se uma condição causará um pânico!

Colocamos o atributo #[should_panic] após o atributo #[test] e antes da função de teste a que ele se aplica. Vamos ver o resultado quando este teste passa:

running 1 test
test tests::greater_than_100 - should panic ... ok

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

Parece bom! Agora, vamos introduzir um bug em nosso código removendo a condição de que a função new entrará em pânico se o valor for maior que 100:

// src/lib.rs
--snip--

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "O valor de Guess deve estar entre 1 e 100, obteve {}.",
                value
            );
        }

        Guess { value }
    }
}

Quando executamos o teste na Listagem 11-8, ele falhará:

running 1 test
test tests::greater_than_100 - should panic ... FAILED

failures:

---- tests::greater_than_100 stdout ----
note: test did not panic as expected

failures:
    tests::greater_than_100

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

Não recebemos uma mensagem muito útil neste caso, mas quando olhamos para a função de teste, vemos que ela está anotada com #[should_panic]. A falha que obtivemos significa que o código na função de teste não causou um pânico.

Testes que usam should_panic podem ser imprecisos. Um teste should_panic passaria mesmo que o teste entrasse em pânico por uma razão diferente da que esperávamos. Para tornar os testes should_panic mais precisos, podemos adicionar um parâmetro expected opcional ao atributo should_panic. O test harness garantirá que a mensagem de falha contenha o texto fornecido. Por exemplo, considere o código modificado para Guess na Listagem 11-9, onde a função new entra em pânico com mensagens diferentes, dependendo se o valor é muito pequeno ou muito grande.

// src/lib.rs
--snip--

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "O valor de Guess deve ser maior ou igual a 1, obteve {}.",
                value
            );
        } else if value > 100 {
            panic!(
                "O valor de Guess deve ser menor ou igual a 100, obteve {}.",
                value
            );
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "menor ou igual a 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

Listagem 11-9: Testando um panic! com uma mensagem de pânico contendo uma substring especificada

Este teste passará porque o valor que colocamos no parâmetro expected do atributo should_panic é uma substring da mensagem com a qual a função Guess::new entra em pânico. Poderíamos ter especificado toda a mensagem de pânico que esperamos, que neste caso seria O valor de Guess deve ser menor ou igual a 100, obteve 200. O que você escolher especificar depende de quanto da mensagem de pânico é exclusivo ou dinâmico e quão preciso você deseja que seu teste seja. Neste caso, uma substring da mensagem de pânico é suficiente para garantir que o código na função de teste execute o caso else if value > 100.

Para ver o que acontece quando um teste should_panic com uma mensagem expected falha, vamos novamente introduzir um bug em nosso código trocando os corpos dos blocos if value < 1 e else if value > 100:

// src/lib.rs
--snip--
if value < 1 {
    panic!(
        "O valor de Guess deve ser menor ou igual a 100, obteve {}.",
        value
    );
} else if value > 100 {
    panic!(
        "O valor de Guess deve ser maior ou igual a 1, obteve {}.",
        value
    );
}
--snip--

Desta vez, quando executarmos o teste should_panic, ele falhará:

running 1 test
test tests::greater_than_100 - should panic ... FAILED

failures:

---- tests::greater_than_100 stdout ----
thread 'main' panicked at 'O valor de Guess deve ser maior ou igual a 1, obteve
200.', src/lib.rs:13:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
      panic message: `"O valor de Guess deve ser maior ou igual a 1, obteve
200."`,
 expected substring: `"menor ou igual a 100"`

failures:
    tests::greater_than_100

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

A mensagem de falha indica que este teste realmente entrou em pânico como esperávamos, mas a mensagem de pânico não incluiu a string esperada 'O valor de Guess deve ser menor ou igual a 100'. A mensagem de pânico que obtivemos neste caso foi O valor de Guess deve ser maior ou igual a 1, obteve 200. Agora podemos começar a descobrir onde está nosso bug!

Usando Result<T, E> em Testes

Nossos testes até agora entram em pânico quando falham. Também podemos escrever testes que usam Result<T, E>! Aqui está o teste da Listagem 11-1, reescrito para usar Result<T, E> e retornar um Err em vez de entrar em pânico:

Nome do arquivo: src/lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("dois mais dois não é igual a quatro"))
        }
    }
}

A função it_works agora tem o tipo de retorno Result<(), String>. No corpo da função, em vez de chamar a macro assert_eq!, retornamos Ok(()) quando o teste passa e um Err com uma String dentro quando o teste falha.

Escrever testes para que eles retornem um Result<T, E> permite que você use o operador de ponto de interrogação no corpo dos testes, o que pode ser uma maneira conveniente de escrever testes que devem falhar se qualquer operação dentro deles retornar uma variante Err.

Você não pode usar a anotação #[should_panic] em testes que usam Result<T, E>. Para afirmar que uma operação retorna uma variante Err, não use o operador de ponto de interrogação no valor Result<T, E>. Em vez disso, use assert!(value.is_err()).

Agora que você conhece várias maneiras de escrever testes, vamos ver o que está acontecendo quando executamos nossos testes e explorar as diferentes opções que podemos usar com cargo test.

Resumo

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