unit testing - Prueba de unidad Haskell
unit-testing (3)
En general, cualquier proyecto significativo de Haskell se ejecuta con Cabal . Esto se ocupa de la construcción, la distribución, la documentación (con la ayuda de eglefino) y las pruebas.
El enfoque estándar es colocar sus pruebas en el directorio de test
y luego configurar un conjunto de pruebas en su archivo .cabal
. Esto se detalla en el manual del usuario . Esto es lo que parece el banco de pruebas para uno de mis proyectos
Test-Suite test-melody
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: test
build-depends: base >=4.6 && <4.7,
test-framework,
test-framework-hunit,
HUnit,
containers == 0.5.*
Luego, en la test/Main.hs
archivo test/Main.hs
import Test.HUnit
import Test.Framework
import Test.Framework.Providers.HUnit
import Data.Monoid
import Control.Monad
import Utils
pushTest :: Assertion
pushTest = [NumLit 1] ^? push (NumLit 1)
pushPopTest :: Assertion
pushPopTest = [] ^? (push (NumLit 0) >> void pop)
main :: IO ()
main = defaultMainWithOpts
[testCase "push" pushTest
,testCase "push-pop" pushPopTest]
mempty
Donde Utils
define algunas interfaces más agradables sobre HUnit .
Para pruebas de peso más ligero, le recomiendo que use QuickCheck . Te permite escribir propiedades cortas y probarlas a través de una serie de entradas aleatorias. Por ejemplo:
-- Tests.hs
import Test.QuickCheck
prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs
Y entonces
$ ghci Tests.hs
> import Test.QuickCheck
> quickCheck prop_reverseReverse
.... Passed Tests (100/100)
Soy nuevo en Haskell y trabajo en pruebas unitarias, sin embargo, encuentro que el ecosistema es muy confuso. Estoy confundido en cuanto a la relación entre HTF y HUnit.
En algunos ejemplos veo que configura casos de prueba, los exporta en una lista de pruebas y luego ejecuta ghci con runTestsTT
(como este ejemplo de HUnit ).
En otros ejemplos, creas un corredor de prueba vinculado al archivo cabal que usa algo de magia de preprocesador para encontrar tus pruebas, como en este ejemplo de git . También parece que las pruebas de HTF necesitan un prefijo test_
o no se ejecutan? Me costó encontrar documentación sobre eso, solo noté el patrón que todos tenían.
De todos modos, ¿alguien puede ayudar a resolver esto por mí? ¿Qué se considera la forma estándar de hacer las cosas en Haskell? ¿Cuáles son las mejores prácticas? ¿Cuál es el más fácil de configurar y mantener?
También soy novato haskeller y esta introducción me ha sido de gran ayuda: " Comenzando con HUnit ". Para resumir, voy a poner aquí un ejemplo de prueba simple del uso de HUnit sin el archivo de proyecto .cabal
:
Supongamos que tenemos el módulo SafePrelude.hs
:
module SafePrelude where
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
podemos poner pruebas en TestSafePrelude.hs
siguiente manera:
module TestSafePrelude where
import Test.HUnit
import SafePrelude
testSafeHeadForEmptyList :: Test
testSafeHeadForEmptyList =
TestCase $ assertEqual "Should return Nothing for empty list"
Nothing (safeHead ([]::[Int]))
testSafeHeadForNonEmptyList :: Test
testSafeHeadForNonEmptyList =
TestCase $ assertEqual "Should return (Just head) for non empty list" (Just 1)
(safeHead ([1]::[Int]))
main :: IO Counts
main = runTestTT $ TestList [testSafeHeadForEmptyList, testSafeHeadForNonEmptyList]
Ahora es fácil ejecutar pruebas usando ghc
:
runghc TestSafePrelude.hs
o hugs
: en este caso, TestSafePrelude.hs
debe renombrarse como Main.hs
(siempre que esté familiarizado con los abrazos) (no olvide cambiar también el encabezado del módulo):
runhugs Main.hs
o cualquier otro compilador haskell
;-)
Por supuesto, hay más que eso en HUnit
, así que realmente recomiendo leer el tutorial sugerido y la Guía del usuario de la biblioteca.
Usted ha respondido la mayoría de sus preguntas, pero también ha preguntado sobre HTF y cómo funciona.
HTF es un marco que está diseñado para ambas pruebas unitarias: es compatible con HUnit (lo integra y lo envuelve para proporcionar funciones adicionales) y las pruebas basadas en propiedades, se integra con la comprobación rápida. Utiliza un preprocesador para ubicar las pruebas para que no tenga que crear una lista manualmente. El preprocesador se agrega a los archivos fuente de prueba con un pragma:
{-# OPTIONS_GHC -F -pgmF htfpp #-}
(Alternativamente, supongo que podrías agregar las mismas opciones a tu propiedad ghc-options
en tu archivo cabal, pero nunca lo intenté, así que no sé si es útil o no).
El preprocesador escanea su módulo para funciones de alto nivel denominadas test_xxxx
o prop_xxxx
y las agrega a una lista de pruebas para el módulo. Puede usar esta lista directamente poniendo una función main
en el módulo y ejecutándolas ( main = htfMain htf_thisModuleTests
) o main = htfMain htf_thisModuleTests
desde el módulo, y tiene un programa de prueba principal para múltiples módulos, que importa los módulos con pruebas y ejecuta todo de ellos:
import {-@ HTF_TESTS @-} ModuleA
import {-@ HTF_TESTS @-} ModuleB
main :: IO ()
main = htfMain htf_importedTests
Este programa puede integrarse con cabal usando la técnica descrita por @jozefg, o cargarse en ghci y ejecutarse de forma interactiva (aunque no en Windows - ver https://github.com/skogsbaer/HTF/issues/60 para más detalles).
Tasty es otra alternativa que proporciona una forma de integrar diferentes tipos de pruebas. No tiene un preprocesador como HTF, pero tiene un módulo que realiza funciones similares con Template Haskell . Al igual que HTF, también se basa en la convención de nombres para identificar sus pruebas (en este caso, case_xxxx
vez de test_xxxx
). Además de las pruebas HUnit y QuickCheck, también tiene módulos para manejar varios otros tipos de prueba.