Testes
Em Rust temos macros para testarmos nossas features, como os macros para "assertions" sendo eles assert!
, assert_eq!
e assert_ne!
. Podemos utiliza-los em nosso código para testar as nossas funcionalidades, utilizando a ideia de módulos podemos criar um módulo especifico para os testes. E para isso utilizamos a opção de compilação #[cfg(test)]
, esta configuração irá definir que é um módulo para testes e nesse módulo nós adicionamos nossos códigos de testes, e para informar que são testes utilizamos outro macro #[test]
.
Este módulo de testes NÃO é compilado em nosso executável final.
Abaixo temos um exemplo da declaração de um módulo de testes já contendo dois testes, utilizando os macros assert!
, assert_eq!
e assert_ne!
:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn deve_ser_verdadeiro() { let maior = 1 > 0; assert!(maior) } #[test] fn devem_ser_iguais() { assert_eq!(2 + 2, 4) } #[test] fn devem_ser_diferentes() { assert_ne!(1 + 1, 10) } } }
Com apenas esses 3 macros de assert
, conseguimos testar muitas coisas, vamos a um exemplo mais complexo.
Temos um cliente que deseja fazer a integração de pagamentos com outro serviço e antes de enviar a requisição para realizar a integração, deve ser feito algumas validações, sendo elas:
- O valor a ser pago deve ser maior que 0.0
- O valor a ser pago deve ser menor ou igual a 1000.0
Vamos começar a escrever os nossos testes, antes de escrevemos nosso código.
#![allow(unused)] fn main() { mod integracao_de_pagamento { pub fn valor_eh_valido(valor: f64) -> bool { todo!() } } #[cfg(test)] mod tests { use super::integracao_de_pagamento; #[test] fn valida_valor_pagamento_acima_de_dez_deve_retornar_verdadeiro() { let valor_valido = integracao_de_pagamento::valor_eh_valido(10.0); assert!(valor_valido); } #[test] fn valida_valor_pagamento_abaixo_de_zero_deve_retornar_falso() { let valor_valido = integracao_de_pagamento::valor_eh_valido(-10.0); assert!(!valor_valido); } #[test] fn valida_valor_pagamento_acima_de_mil_deve_retornar_falso() { let valor_valido = integracao_de_pagamento::valor_eh_valido(1001.0); assert!(!valor_valido) } } }
NOTA: Como tests
é um módulo ele deve importar os outros módulos
NOTA 2: O nome do módulo não necessariamente deve ser "tests"
Para executar o código acima, devemos usar o comando cargo test
, este comando ira executar todos os testes do projeto. Como esperado todos os testes vão falhar! Vamos escrever o nosso código.
#![allow(unused)] fn main() { mod integracao_de_pagamento { pub fn valor_eh_valido(value: f64) -> bool { valor > 0.0 } } }
Ao executar o comando cargo test
temos a seguinte saída:
running 3 tests
test tests::valida_valor_pagamento_acima_de_dez_deve_retornar_verdadeiro ... ok
test tests::valida_valor_pagamento_abaixo_de_zero_deve_retornar_falso ... ok
test tests::valida_valor_pagamento_acima_de_mil_deve_retornar_falso ... FAILED
failures:
---- tests::valida_valor_pagamento_acima_de_mil_deve_retornar_falso stdout ----
thread 'tests::valida_valor_pagamento_acima_de_mil_deve_retornar_falso' panicked at 'assertion failed: !valor_valido', src/lib.rs:29:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::valida_valor_pagamento_acima_de_mil_deve_retornar_falso
test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Dois testes passaram e um teste falhou, pelas regras, devemos validar se o valor é menor ou igual a 1000.00 para ser valido. Vamos alterar a implementação.
#![allow(unused)] fn main() { mod integracao_de_pagamento { pub fn valor_eh_valido(value: f64) -> bool { valor > 0.0 && valor <= 1000.0 } } }
E agora temos todos os testes funcionando.
Finished test [unoptimized + debuginfo] target(s) in 0.37s
Running unittests (target/debug/deps/testes-83f2e46ed2822f78)
running 3 tests
test tests::valida_valor_pagamento_acima_de_mil_deve_retornar_falso ... ok
test tests::valida_valor_pagamento_acima_de_dez_deve_retornar_verdadeiro ... ok
test tests::valida_valor_pagamento_abaixo_de_zero_deve_retornar_falso ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests testes
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Usamos apenas uma opção de assert
, vamos utilizar outro teste.
Vamos usar aquela conta que todo mundo ama fazer no ensino médio, encontrar os valores de uma equação de segundo grau, e para isso usaremos a Fórmula de Bhaskara e também iremos aproveitar para explicar outro conceito que é quando um teste deve causar um panic
.
Vamos às regras da Fórmula de Bhaskara:
- Para calcular Delta usaremos a fórmula
b²-4*a*c
- Delta não pode ser negativo
- Precisamos ter três valores (a, b e c)
- Temos que devolver duas raízes
- Para calcular as raízes usaremos (-b +- √delta) / 2*a
Vamos escrever nossos testes:
#![allow(unused)] fn main() { mod calculadora { pub fn calcular(a: f64, b: f64, c: f64) -> Result<(f64, f64), String> { todo!() } pub(super) fn calcula_delta(a: f64, b: f64, c: f64) -> Result<f64, String> { todo!() } } #[cfg(test)] mod tests { use super::calculadora; #[test] #[should_panic] fn calcular_delta_negativo_esperado_error() { calculadora::calcula_delta(1.0, 2.0, 3.0).unwrap(); } #[test] fn calcular_delta_nao_deve_haver_erro() { let delta = calculadora::calcula_delta(8.0, 7.0, -2.0).unwrap(); assert_eq!(113.0, delta, "o valor de desta esta errado") } #[test] fn deve_calcular_as_duas_raizes() { let resultado = calculadora::calcular(1.0, 3.0, -2.0).unwrap(); assert_eq!((0.5615528128088303, -3.5615528128088303), resultado); } #[test] #[should_panic(expected = "nao contem raiz real")] fn calcular_raizes_com_delta_negativo_esperado_error() { calculadora::calcular(1.0, 2.0, 3.0).unwrap(); } } }
Ao executar o comando para os testes, teremos um teste que ira passar, que é o teste calcular_delta_negativo_esperado_error
, isso acontece porque ele tem que causar um panic
e o macro todo!
causa o panic, porém não é o que queremos, então no último testes colocamos a mensagem que esperamos no panic!
, que é "nao contem raiz real".
Vamos seguir com a implementação do nosso cálculo.
#![allow(unused)] fn main() { mod calculadora { pub fn calcular(a: f64, b: f64, c: f64) -> Result<(f64, f64), String> { todo!() } pub(super) fn calcula_delta(a: f64, b: f64, c: f64) -> Result<f64, String> { let delta = (b * b) - 4.0 * a * c; if delta < 0.0 { return Err("nao contem raiz real".to_string()) } Ok(delta) } } }
Agora temos dois testes que passam e dois testes que falham. Veja a saída no console:
Finished test [unoptimized + debuginfo] target(s) in 0.38s
Running unittests (target/debug/deps/testes-83f2e46ed2822f78)
running 4 tests
test tests::calcular_delta_nao_deve_haver_erro ... ok
test tests::calcular_raizes_com_delta_negativo_esperado_error - should panic ... FAILED
test tests::calcular_delta_negativo_esperado_error - should panic ... ok
test tests::deve_calcular_as_duas_raizes ... FAILED
failures:
---- tests::calcular_raizes_com_delta_negativo_esperado_error stdout ----
thread 'tests::calcular_raizes_com_delta_negativo_esperado_error' panicked at 'not yet implemented', src/lib.rs:4:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"not yet implemented"`,
expected substring: `"nao contem raiz real"`
---- tests::deve_calcular_as_duas_raizes stdout ----
thread 'tests::deve_calcular_as_duas_raizes' panicked at 'not yet implemented', src/lib.rs:4:9
failures:
tests::calcular_raizes_com_delta_negativo_esperado_error
tests::deve_calcular_as_duas_raizes
test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Como esperado aquele método que esta esperando uma mensagem especifica do panic, continua falhando. Vamos implementa-lo.
#![allow(unused)] fn main() { mod calculadora { pub fn calcular(a: f64, b: f64, c: f64) -> Result<(f64, f64), String> { let delta = calcula_delta(a, b, c)?; let menos_b = -b; let x1 = (menos_b + delta.sqrt()) / 2.0 * a; let x2 = (menos_b - delta.sqrt()) / 2.0 * a; Ok((x1, x2)) } pub(super) fn calcula_delta(a: f64, b: f64, c: f64) -> Result<f64, String> { let delta = (b * b) - 4.0 * a * c; if delta < 0.0 { return Err("nao contem raiz real".to_string()) } Ok(delta) } } }
Agora ao executar os testes teremos sucesso:
Finished test [unoptimized + debuginfo] target(s) in 0.32s
Running unittests (target/debug/deps/testes-83f2e46ed2822f78)
running 4 tests
test tests::calcular_delta_nao_deve_haver_erro ... ok
test tests::calcular_delta_negativo_esperado_error - should panic ... ok
test tests::deve_calcular_as_duas_raizes ... ok
test tests::calcular_raizes_com_delta_negativo_esperado_error - should panic ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests testes
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s