Buscar

Testes e Depuração em Haskell

Prévia do material em texto

Testes e depuração em Haskell
 
Autor: Lucas Inojosa
Data: 14/04/2011
v1.0
 
Um assunto no qual senti dificuldade em Haskell quando paguei PLC, especialmente no projeto, 
foi como debugar as funções e testá-las. Em linguagens orientadas a objeto e imperativas, temos o 
famoso recurso de "imprimir na tela". Em haskell também temos (putStrLn "olá, mundo"), porém é uma 
função IO e só pode ser chamada por funções IO, e não funciona em funções puras. Aqui vou apresentar 
um tutorial bem básico sobre isso, também introduzindo a biblioteca HUnit.
A função Debug.Trace.trace
Haskell nos oferece outro recurso de impressão de resultados na tela em funções puras, a 
função "trace :: String -> a -> a". Esta função recebe um argumento String, que será impresso na tela, e 
um argumento de qualquer outro tipo, que simplesmente será retornado no final. Um exemplo: vou definir 
uma função que eleva um numero a outro.
 
potencia :: Int -> Int -> Int 
potencia base 0 = 1 
potencia base exp = base * resultadoRecursivo 
 where 
 resultadoRecursivo = potencia base exp-1 
 
Vamos dizer que a cada fim de chamada recursiva, eu quero que o resultado da potencia seja 
impressa na tela. Faríamos assim:
 
import Debug.Trace 
 
potencia :: Int -> Int -> Int 
potencia base 0 = 1 
potencia base exp = base * (trace ("o resultado parcial eh: " ++ (show 
resultadoRecursivo)) resultadoRecursivo) 
 where 
 resultadoRecursivo = potencia base exp-1 
 
Essas 2 funções têm comportamento semântico idênticos, excluindo a ação de E/S de imprimir 
na tela. Vamos analisar a função trace:
 
trace ("o resultado parcial eh: " ++ (show resultadoRecursivo)) 
resultadoRecursivo 
 
O que ela faz? Simples. O primeiro argumento inteiro (até a função show) é uma string que exibe 
o resultado parcial recursivo, e o segundo argumento é o próprio resultado em si. A função vai imprimir a 
primeira string e vai retornar exatamente o resultado recursivo (o segundo argumento). Testem para ver!
Com isso, é possível imprimir resultados obtidos dentro de funções puras.
 
Testes unitários com HUnit
Quem já pagou Engenharia de Software deve estar familiarizado com testes unitários e a 
biblioteca JUnit. O HUnit segue a lógica e o padrão dos testes unitários do JUnit, porém é implementada 
em Haskell. Será bastante útil no projeto. Segue um exemplo:
Digamos que nós temos uma função que calcula fatorial, e queremos testar seu comportamento:
 
fatorial :: Int -> Int 
fatorial 0 = 1 
fatorial n 
 | n < 0 = -1 
 | otherwise = n * fatorial (n-1) 
 
Um teste completo da funcionalidade desta função ficaria mais ou menos assim:
 
import Test.HUnit 
 
Importado o HUnit, vamos construir alguns casos de testes:
 
teste1 = TestCase (assertEqual "Teste do fatorial de 0" (fatorial 0) 1) 
teste2 = TestCase (assertEqual "Teste do fatorial de 1" (fatorial 1) 1) 
teste3 = TestCase (assertEqual "Teste do fatorial de 2" (fatorial 2) 2) 
teste4 = TestCase (assertEqual "Teste do fatorial de 3" (fatorial 3) 6) 
teste5 = TestCase (assertEqual "Teste do fatorial de 4" (fatorial 4) 24) 
teste6 = TestCase (assertEqual "Teste do fatorial de 5" (fatorial 5) 120) 
teste7 = TestCase (assertEqual "Teste do fatorial de 7" (fatorial 7) 5040) 
teste8 = TestCase (assertEqual "Teste do fatorial de 10" (fatorial 10) 
3628800) 
teste9 = TestCase (assertEqual "Teste do fatorial de 15" (fatorial 15) 
2004310016) 
 
Cada caso de teste desse testa se um fatorial de um dado número é igual ao resultado 
esperado. Se, por exemplo, na 2ª linha o "fatorial 1" retornar algo diferente de 1, ele indica que o teste 
falhou e não passou. Vamos construir agora mais alguns casos de testes.
 
teste10 = TestCase (assertEqual "Teste do fatorial de um numero negativo 1" 
(fatorial (-1)) (-1)) 
teste11 = TestCase (assertEqual "Teste do fatorial de um numero negativo 2" 
(fatorial (-25)) (-1)) 
teste12 = TestCase (assertEqual "Teste do fatorial de um numero negativo 3" 
(fatorial (-150)) (-1)) 
 
Como foi definido na função, se um número negativo for passado, retorne -1. Estou testando se 
este comportamento vai mesmo funcionar. Agora vamos combinar os testes.
 
testes = TestList [teste1, teste2, teste3, teste4, teste5, teste6, teste7, 
teste8, teste9, teste10, teste11, teste12] 
 
testar :: IO () 
testar = do 
 runTestTT testes 
 return () 
 
 
Agora compile com o ghci e rode a função testar. A função runTestTT roda esta lista de testes e exibe na 
tela o resultado.
Existe grandes vantagens em se usar testes automatizados, como a possibilidade de rodar os testes 
novamente para verificar se uma alteração no programa não prejudicou o que já estava funcionando 
(bem comum!!!). É uma prática bastante usada na Engenharia de Software e o ponto central de 
Desenvolvimento Orientado a Testes, e é uma boa prática de programação. Este foi um tutorial básico 
do básico sobre o HUnit e testes automatizados, há um guia mais detalhado sobre o HUnit na página 
dele:
 
http://hunit.sourceforge.net/HUnit-1.0/Guide.html.

Continue navegando