# Propriedades e testes com QuickCheck

Propriedades em vez de exemplos, geradores automáticos e leitura de contraexemplos.

Página: https://resumos.rgo.pt/cadeiras/pfl/testes-quickcheck/

Um teste de exemplo verifica um caso: `reverse [1,2,3]` dá `[3,2,1]`. Uma **propriedade** afirma uma lei para todos os casos: inverter duas vezes devolve a lista original. O **QuickCheck** testa a propriedade em centenas de casos aleatórios por ti. Em vez de inventares exemplos, inventas leis, e a ferramenta trata de as tentar partir.

## A primeira propriedade

Propriedades são funções que devolvem `Bool`, com tipos concretos para a ferramenta saber o que gerar:

```
import Test.QuickCheck

prop_revRev :: [Int] -> Bool
prop_revRev xs = reverse (reverse xs) == xs
```

No GHCi, com o pacote QuickCheck instalado:

```
ghci> quickCheck prop_revRev
+++ OK, passed 100 tests.
```

A ferramenta gerou 100 listas aleatórias[1](https://resumos.rgo.pt/cadeiras/pfl/testes-quickcheck/#user-content-fn-cem) (vazias, curtas, longas, com negativos e repetidos) e a igualdade valeu em todas. Repara no que isto compra: um teste de exemplo cobre a lista que escreveste; a propriedade cobre as listas que nunca te lembrarias de escrever, incluindo `[]`, que apanha metade dos erros de recursão.

## Quando falha: o contraexemplo

Uma propriedade falsa dá um contraexemplo mínimo. Afirma que ordenar é a identidade:

```
import Data.List (sort)

prop_sortId :: [Int] -> Bool
prop_sortId xs = sort xs == xs
```

```
ghci> quickCheck prop_sortId
*** Failed! Falsifiable (after 3 tests):
[1,0]
```

O QuickCheck responde que a propriedade é falsificável e mostra a testemunha: `[1,0]`, já **encolhida** (shrinking) de um caso aleatório maior até ao mínimo que ainda falha. O número de testes varia de corrida para corrida; a lista curta fora de ordem é o essencial. O fluxo de trabalho é este: escreve a propriedade, vê-a falhar, lê o contraexemplo, corrige a ideia. O diagrama resume o ciclo, que recomeça a cada correção:

![Ciclo do QuickCheck: escrever a propriedade, gerar casos, correr, encolher a falha até ao contraexemplo mínimo e corrigir.](https://resumos.rgo.pt/cadeiras/pfl/testes-quickcheck/figura-1.svg)

Compara as duas sessões lado a lado: a propriedade verdadeira passa calada, a falsa responde com a testemunha mínima.

```
ghci> quickCheck prop_revRev
+++ OK, passed 100 tests.
```

```
ghci> quickCheck prop_sortId
*** Failed! Falsifiable (after 3 tests):
[1,0]
```

```
prop_sortSort :: [Int] -> Bool
prop_sortSort xs = sort (sort xs) == sort xs
```

```
ghci> quickCheck prop_sortSort
+++ OK, passed 100 tests.
```

## Geradores e condições

Por omissão, o QuickCheck gera valores **arbitrários** do tipo pedido: inteiros pequenos e grandes, listas de vários tamanhos, carateres Unicode. Quando a propriedade só faz sentido sob uma condição, impõe-na com `==>`:

```
prop_divMod :: Int -> Int -> Property
prop_divMod x y = y /= 0 ==> (x `div` y) * y + (x `mod` y) == x
```

Os casos com `y == 0` são descartados antes de contar. Se a condição for demasiado rara, a ferramenta avisa que não conseguiu casos suficientes, e aí o remédio é um gerador à medida com `forAll`. Para esta cadeira, chega saberes que o gerador existe e que a seta `==>` filtra.

Um gerador à medida é uma ação que produz valores com a forma que queres. Para testar que o máximo de um par ordenado é o segundo elemento, gera pares já ordenados:

```
parOrdenado :: Gen (Int, Int)
parOrdenado = do
  x <- arbitrary
  y <- arbitrary `suchThat` (>= x)
  return (x, y)

prop_maxDireita :: Property
prop_maxDireita = forAll parOrdenado $ \(x, y) -> max x y == y
```

O `suchThat` filtra os valores gerados até um `y` maior ou igual a `x`; o `forAll` corre a propriedade só nesses pares. Como `x <= y` por construção, `max x y` vale sempre `y` e a propriedade passa.

De onde vêm as propriedades

Boas fontes: leis algébricas (`x + 0 == x`), idempotência (`sort (sort xs) == sort xs`), inversas (`reverse (reverse xs) == xs`) e modelos de referência (a tua função dá o mesmo que a versão lenta e óbvia). Se não consegues enunciar nenhuma lei sobre a função, é sinal de que ainda não percebeste o que ela promete.

## Para saber mais

[Vídeo: QuickCheck para programadores imperativos](https://www.youtube.com/watch?v=pD7GmxBA_Qw)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=pD7GmxBA_Qw)

## Notas de rodapé

1.  Os 100 casos são a omissão da ferramenta; podes pedir mais com argumentos de configuração. Detalhes na [documentação do QuickCheck no Hackage](https://hackage.haskell.org/package/QuickCheck). [Voltar](https://resumos.rgo.pt/cadeiras/pfl/testes-quickcheck/#user-content-fnref-cem)
