Ir para o conteúdo

QuickCheck

Quando desenvolvemos software, é imprescindível que o testemos para verificar a corretude de nosso código, ou pelo menos ganhar confiança em sua corretude. Executar baterias de testes manualmente a cada modificação é um processo tedioso e que certamente levará a testes não serem executados. Além disso, à medida que nossas funções ficam mais complexas, gastaríamos mais e mais tempo definindo e executando testes. Uma ferramenta que nos ajuda a fazer nestas tarefas é a biblioteca QuickCheck. Para entender como usar a QuickCheck, considere as duas seguintes funções.

celsius2fahrenheit c = c * 9 / 5 + 32 

fahrenheit2celsius f = (f - 32) * 5/9

Elas são obviamente inversas uma da outra, o que quer dizer que se eu aplicar um valor qualquer à primeira função e então aplicar o resultado na segunda, eu deveria recuperar o valor original. Por exemplo, se o valor qualquer é 100, 100 == fahrenheit2celsius (celsius2fahrenheit 100). Pois a QuickCheck nos permite escrever exatamente este tipo de afirmação e tê-la testada automaticamente para um conjunto de valores aleatórios. Obviamente, mesmo se o código passar nos testes gerados pelo QuickCheck, não quer dizer que outros valores não resultariam em erros. Contudo, nossa confiança na corretude aumentaria.

Para testar as funções acima escreveríamos então a propriedade como uma função que retorna um booleano. A propriedade tem um nome iniciado em prop_, mas isso é apenas uma convenção. A definição recebe como parâmetro um valor c, converte para Celsius e de volta para Fahrenheit, e confere se o valor resultante é igual ao valor de entrada. Observe que o resultado da função é o resultado da comparação com o valor inicial, o que é equivalente mas muito mais limpo do que fazer um teste do tipo if Condição then True else False.

prop_C2f2C c = fahrenheit2celsius (celsius2fahrenheit c) == c

Para usar a biblioteca, precisamos importá-la, no início do arquivo, com um import Test.QuickCheck. Assim, o arquivo ficará assim.

import Test.QuickCheck

celsius2fahrenheit c = c * 9 / 5 + 32 

fahrenheit2celsius f = (f - 32) * 5/9

prop_C2f2C c = fahrenheit2celsius (celsius2fahrenheit c) == c

Para testar o código, execute o ghci carregando o QuickCheck. A forma mais simples de fazê-lo é usando stack: stack ghci --package QuickCheck. Agora carregue seu programa, usando :l f2c2f no meu caso e execute o teste

> quickCheck prop_C2f2C
*** Failed! Falsified (after 2 tests and 4 shrinks):
-0.1

Ooops! O teste falhou para o valor -0.1. Mas por quê? Vejamos cada função isoladamente.

> celsius2fahrenheit (-0.1)
31.82
> fahrenheit2celsius 31.82
-9.999999999999984e-2

Os valores são próximos, mas não são iguais, por causa de problemas de arredondamento causados pela imprecisão dos tipos utilizados. O problema está no teste, pois é impossível para o computador representar certos valores e estes erros de aproximação ocorrerão. Assim, uma estratégia melhor é definir um operador quase igual, para comparar valores com pequenos erros.

import Test.QuickCheck

celsius2fahrenheit c = c * 9 / 5 + 32 

fahrenheit2celsius f = (f - 32) * 5/9

prop_C2f2C c = fahrenheit2celsius (celsius2fahrenheit c) ~== c

l ~== r = abs (l - r) < ε
    where ε = 10e-10

Com esta nova definição, nosso teste agora passa com sucesso.

> quickCheck prop_C2f2C
+++ OK, passed 100 tests.