Test unitaire Haskell

Je suis nouveau chez haskell et je travaille sur les tests unitaires, mais je trouve l'écosystème très déroutant. Je suis confus quant à la relation entre HTF et HUnit.

Dans certains exemples, je vois que vous configurez des cas de test,les exportez dans une liste de tests, puis exécutez dans ghci avec runTestsTT (comme cet exemple HUnit).

Dans d'autres exemples, vous créez un coureur de test lié au fichier cabal qui utilise de la magie du préprocesseur pour trouver vos tests comme dans cet exemple git . En outre, il semble que les tests HTF doivent être préfixés avec test_ ou ils ne sont pas exécutés? J'ai eu du mal à trouver de la documentation à ce sujet, je viens de remarquer le modèle que tout le monde avait.

De toute façon, quelqu'un peut-il aider à régler cela pour moi? Qu'est-ce qui est considéré comme la façon standard de faire les choses à Haskell? Quelles sont les meilleures pratiques? Quel est le plus facile à mettre en place et à entretenir?

62
demandé sur devshorts 2013-12-02 18:55:53

3 réponses

Généralement, tout projet Haskell significatif est exécuté avec Cabal . Cela prend en charge la construction, la distribution, la documentation (avec l'aide de l'aiglefin) et les tests.

L'approche standard consiste à placer vos tests dans le répertoire test, puis à configurer une suite de tests dans votre fichier .cabal. Ceci est détaillé dans le manuel d'utilisation . Voici à quoi ressemble la suite de tests pour l'un de mes projets

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.*

, Puis dans le fichier 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

Utils définit quelques interfaces plus agréables sur HUnit .

Pour les tests plus légers, je vous recommande fortement d'utiliser QuickCheck . Il vous permet d'écrire des propriétés courtes et de les tester sur une série d'entrées aléatoires. Par exemple:

 -- Tests.hs
 import Test.QuickCheck

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

, puis

 $ ghci Tests.hs
 > import Test.QuickCheck
 > quickCheck prop_reverseReverse
 .... Passed Tests (100/100)
41
répondu jozefg 2018-03-18 10:43:40

Je suis aussi débutant haskeller et j'ai trouvé cette introduction vraiment utile: "Mise en route avec HUnit ". Pour résumer, je vais mettre ici un exemple de test simple de L'utilisation de HUnit sans .cabal Fichier de projet:

Supposons que nous avons le module SafePrelude.hs:

module SafePrelude where

safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

, Nous pouvons mettre des tests dans TestSafePrelude.hs comme suit:

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]

Maintenant, il est facile d'exécuter des tests en utilisant ghc:

runghc TestSafePrelude.hs

Ou hugs - dans ce cas - TestSafePrelude.hs doit être renommé Main.hs (aussi loin que je suis familier avec des câlins) (n'oubliez pas de changer l'en-tête du module aussi):

runhugs Main.hs

Ou tout autre compilateur haskell; -)

Bien sûr, il y en a plus que dans HUnit, donc je recommande vraiment de lire le tutoriel et la bibliothèque suggérésGuide de l'Utilisateur .

30
répondu paluh 2015-09-24 11:32:00

Vous avez eu des réponses à la plupart de vos questions, mais vous avez aussi posé des questions sur le Fass et sur son fonctionnement.

HTF est un framework conçu pour les tests unitaires - il est rétrocompatible avec HUnit (il l'intègre et l'enveloppe pour fournir des fonctions supplémentaires)-et les tests basés sur les propriétés-il s'intègre à quickcheck. Il utilise un préprocesseur pour localiser les tests afin que vous n'ayez pas à construire manuellement une liste. Le préprocesseur est ajouté à vos fichiers source de test à l'aide d'un pragma:

{-# OPTIONS_GHC -F -pgmF htfpp #-}

(alternativement, je suppose que vous pouvez ajouter les mêmes options à votre propriété ghc-options dans votre fichier cabal, mais je n'ai jamais essayé cela, donc je ne sais pas si c'est utile ou non).

Le préprocesseur scanne votre module pour les fonctions de niveau supérieur nommé test_xxxx ou prop_xxxx et ajoute à une liste de tests pour le module. Vous pouvez soit utiliser cette liste directement en mettant une fonction main dans le module et en les exécutant (main = htfMain htf_thisModuleTests) ou les exporter depuis le module, et avoir un programme de test principal pour plusieurs modules, qui importe les modules avec des tests et les exécute Tous:

import {-@ HTF_TESTS @-} ModuleA
import {-@ HTF_TESTS @-} ModuleB
main :: IO ()
main = htfMain htf_importedTests

Ce programme peut être intégré à cabal en utilisant la technique décrite par @ jozefg, ou chargé dans ghci et exécuté de manière interactive (mais pas sous Windows-voir https://github.com/skogsbaer/HTF/issues/60 pour plus de détails).

Tasty est une autre alternative qui permet d'intégrer différents types de tests. Il n'a pas de préprocesseur comme HTF, mais a un module que effectue des fonctions similaires en utilisant le modèle Haskell . Comme HTF, il s'appuie également sur la convention de nommage pour identifier vos tests (dans ce cas, case_xxxx plutôt que test_xxxx). En plus des tests HUnit et QuickCheck, Il dispose également de modules pour gérer un certain nombre d'autres types de tests.

4
répondu Jules 2016-03-11 15:31:17