Playlist do curso: https://www.youtube.com/playlist?list=PLYItvall0TqJ25sVTLcMhxsE0Hci58mpQ
Uma parte importante da Engenharia de Software é o teste de seu produto final. Dado que o programa compilou sem erros, ele faz o que é esperado?
O Haskell permite, em algumas situações, provar matematicamente que seu programa está correto (usando indução).
Outra forma de verificar a corretude é fazer testes de entrada e saída das funções criadas e verificar se elas apresentam as propriedades esperadas.
Se você criou um novo algoritmo de ordenação, que propriedades são esperadas?
Vamos criar nosso primeiro projeto completo com o stack
:
> stack new quickcheck simple
quickcheck.cabal
e acrescente o seguinte ao
final da linha build-depends
:build-depends: base >= 4.7 && <5, QuickCheck
Digite:
> stack setup
> stack build
Uma famosa implementação no estilo funcional de QuickSort é mostrada abaixo:
Main.hs
import Test.QuickCheck
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort lhs ++ [x] ++ qsort rhs
where
lhs = [e | e <- xs, e < x]
rhs = [e | e <- xs, e > x]
Esse código contém um erro!
Vamos testar uma primeira propriedade de algoritmos de ordenação: idempotência .
Queremos mostrar que qsort (qsort xs) == qsort xs
:
prop_idempotencia :: Ord a => [a] -> Bool
prop_idempotencia xs = qsort (qsort xs) == qsort xs
Vamos testar essa função no ghci (use stack ghci
no diretório do seu projeto):
> :r
> prop_idempotencia [1]
True
> prop_idempotencia [1,2,3,4]
True
> prop_idempotencia [3,2,4,1]
True
> prop_idempotencia [4,3,2,1]
True
> prop_idempotencia []
True
Outra propriedade é que o tamanho da lista seja o mesmo após a execução do algoritmo:
prop_length :: Ord a => [a] -> Bool
prop_length xs = length (qsort xs) == length xs
> :r
> prop_length [1]
True
> prop_length [1,2,3,4]
True
> prop_length [3,2,4,1]
True
> prop_length [4,3,2,1]
True
> prop_length []
True
Os casos de teste utilizado são representativos?
A biblioteca QuickCheck
automatiza a geração de dados para
testes (e faz outras coisas úteis também).
> quickCheck prop_idempotencia
+++ OK, passed 100 tests.
> quickCheck prop_length
*** Failed! Falsifiable (after 4 tests):
[(),()]
Oops!
A biblioteca QuickCheck gera casos de testes progressivos, começando de casos simples até casos mais complexos em busca de erros.
Ao encontrar um erro, ele retorna a instância mais simples que deu errado.
Para entender melhor vamos executar essa função para listas de inteiros:
> quickCheck (prop_length :: [Int] -> Bool)
*** Failed! Falsifiable (after 5 tests and 1 shrink):
[1,1]
> qsort [1,1]
[1]
Basta alterar para não descartar os elementos iguais a x:
import Test.QuickCheck
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort lhs ++ [x] ++ qsort rhs
where
lhs = [e | e <- xs, e <= x]
rhs = [e | e <- xs, e > x]
Agora sim! 🎉
> quickCheck (prop_length :: [Int] -> Bool)
+++ OK, passed 100 tests.
filter
import Test.QuickCheck
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort lhs ++ [x] ++ qsort rhs
where
lhs = filter (<=x) xs
rhs = filter (>x) xs
Outra propriedade é que primeiro elemento da lista é igual ao mínimo:
prop_minimum :: Ord a => [a] -> Bool
prop_minimum xs = head (qsort xs) == minimum xs
Vamos testar essa função no ghci (use stack ghci
):
> quickCheck prop_minimum
*** Failed! Exception:
'Prelude.head: empty list' (after 1 test):
[]
Tanto a função minimum
quanto a função head
retornam erro em listas
vazias, podemos especificar que não queremos testar instâncias nulas com
o operador ==>
(implicação):
prop_minimum :: Ord a => [a] -> Property
prop_minimum xs = not (null xs)
==> head (qsort xs) == minimum xs
Esse operador retorna uma propriedade interpretável pelo quickCheck.
stack ghci
):> quickCheck prop_minimum
+++ OK, passed 100 tests.
Finalmente, se temos um algoritmo que cumpre a mesma tarefa e temos certeza de que está correto, podemos usá-lo na comparação:
import Data.List -- sort
prop_model :: Ord a => [a] -> Bool
prop_model xs = qsort xs == sort xs
True
caso o número
recebido como parâmetro seja par, e False
caso contrário.par x = x `mod` 2 == 0
prop_alternanciaParImpar :: Integral a => a -> Bool
prop_alternanciaParImpar n = par n /= par (n + 1)
stack ghci
):
> quickCheck prop_alternanciaParImpar
+++ OK, passed 100 tests.
Como já temos a função par
, é um tanto desnecessário
definir a função ímpar como abaixo. Mas o exemplo é interessante
para mostrar o uso da função ==>
(implicação)
Quero testar com a propriedade: Se n é par logo não é ímpar
impar x = x `rem` 2 == 1
prop_seImparNaoPar n = par n ==> not (impar n)
fatorial:: Integral a => a -> a
fatorial n
| n == 1 = 1
| otherwise = n * fatorial (n - 1)
prop_fatorialNFatorialNMaisUm n =
fatorial n * (n + 1) == fatorial (n + 1)
fatorial:: Integral a => a -> a
fatorial n
| n == 1 = 1
| otherwise = n * fatorial (n - 1)
prop_fatorialNFatorialNMaisUm n =
fatorial n * (n + 1) == fatorial (n + 1)
fim comentario –>
fatorial:: Integral a => a -> a
fatorial n
| n == 0 = 1
| otherwise = n * fatorial (n - 1)
prop_fatorialNFatorialNMaisUm n =
fatorial n * (n + 1) == fatorial (n + 1)
==>
como anteriormente, mas há um
jeito melhor!fatorial:: Integral a => a -> a
fatorial n
| n == 0 = 1
| otherwise = n * fatorial (n - 1)
prop_fatorialNFatorialNMaisUm (NonNegative n) =
fatorial n * (n + 1) == fatorial (n + 1)
Vamos utilizar o QuickCheck para verificar a conjectura
Lembre-se o QuickCheck dizer que algo passou em X testes apenas quer dizer que ele \textbf{não encontrou} nenhum contra-exemplo para a propriedade sendo testada, não que a propriedade tenha sido provada.
Isso é análogo a Unit Tests, onde os testes passarem não provam a ausência de bugs.
collatz :: Integral a => a -> a
collatz 1 = 1
collatz n
| par n = collatz (n `div` 2)
| otherwise = collatz (3 * n + 1)
prop_collatz (NonNegative n) =
collatz n == 1
collatz :: Integral a => a -> a
collatz 1 = 1
collatz n
| par n = collatz (n `div` 2)
| otherwise = collatz (3 * n + 1)
prop_collatz (Positive n) =
collatz n == 1
Então temos:
> quickCheck prop_collatz
+++ OK, passed 100 tests.
> quickCheckWith stdArgs {maxSuccess = 5000} prop_collatz
+++ OK, passed 5000 tests.
Em breve veremos records e a sintaxe acima.
Estes slides foram preparados para os cursos de Paradigmas de Programação e Desenvolvimento Orientado a Tipos na UFABC.
Este material pode ser usado livremente desde que sejam mantidos, além deste aviso, os créditos aos autores e instituições.