Подтвердить что ты не робот

Тестирование Haskell

Я новичок в haskell и работаю над модульным тестированием, однако считаю, что экосистема очень запутанна. Я смущен относительно отношений между HTF и HUnit.

В некоторых примерах я вижу, что вы настраиваете тестовые примеры, экспортируете их в список тестов, а затем запускаете в ghci с помощью runTestsTT (например этот пример HUnit).

В других примерах вы создаете тестовый бегун, привязанный к кабальному файлу, который использует некоторую магию препроцессора, чтобы найти ваши тесты, как в этом git example. Также кажется, что HTF-тесты должны иметь префикс с test_ или они не запускаются? Мне было трудно найти документацию по этому поводу, я просто заметил шаблон, который у всех был.

В любом случае, может кто-то поможет разобраться в этом? Что считается стандартным способом делать вещи в Haskell? Каковы наилучшие методы? Что проще всего настроить и поддерживать?

4b9b3361

Ответ 1

Как правило, любой значительный проект Haskell запускается с помощью cabal. Это заботится о строительстве, распространении, документации (с помощью пикши) и тестировании.

Стандартный подход заключается в том, чтобы поместить ваши тесты в каталог test, а затем настроить тестовый набор в вашем файле .cabal. Это подробно описано в руководстве пользователя. Вот какой набор тестов для одного из моих проектов выглядит как

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

Затем в файле 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 определяет более приятные интерфейсы над HUnit.

Для более легкого тестирования веса я настоятельно рекомендую использовать QuickCheck. Это позволяет вам писать короткие свойства и тестировать их по серии случайных входов. Например:

 -- Tests.hs
 import Test.QuickCheck

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

И затем

 $ ghci Tests.hs
 > import Test.QuickCheck
 > quickCheck prop_reverseReverse
 .... Passed Tests (100/100)

Ответ 2

Я также новичок haskeller, и я нашел это введение очень полезным: "Начало работы с HUnit". Подводя итог, я приведу здесь простой пример тестирования использования HUnit без файла .cabal проекта:

Предположим, что мы имеем модуль SafePrelude.hs:

module SafePrelude where

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

мы можем поместить тесты в TestSafePrelude.hs следующим образом:

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]

Теперь легко выполнить тесты с помощью ghc:

runghc TestSafePrelude.hs

или hugs - в этом случае TestSafePrelude.hs нужно переименовать в Main.hs (насколько я знаком с объятиями) (не забудьте также изменить заголовок модуля):

runhugs Main.hs

или любой другой haskell компилятор; -)

Конечно, в HUnit есть больше, поэтому я действительно рекомендую прочитать предлагаемый учебник и библиотеку Руководство пользователя.

Ответ 3

У вас были ответы на большинство ваших вопросов, но вы также спросили о HTF и как это работает.

HTF - это структура, предназначенная для модульного тестирования - она ​​обратно совместима с HUnit (она объединяет и обертывает ее предоставляют дополнительные функции) - и тестирование на основе свойств - оно интегрируется с quickcheck. Он использует препроцессор для поиска тестов, так что вам не нужно вручную создавать список. Препроцессор добавляется в ваши тестовые исходные файлы с помощью прагмы:

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

(в качестве альтернативы, я думаю, вы могли бы добавить те же параметры к свойству ghc-options в свой файл cabal, но я никогда не пробовал это, поэтому не знаю, полезно ли это или нет).

Препроцессор сканирует ваш модуль для функций верхнего уровня с именем test_xxxx или prop_xxxx и добавляет их в список тестов для модуля. Вы можете использовать этот список напрямую, поместив в модуль функцию main и запустив их (main = htfMain htf_thisModuleTests) или экспортируя их из модуля и имея основную тестовую программу для нескольких модулей, которая импортирует модули с помощью тестов и запусков все из них:

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

Эта программа может быть интегрирована с cabal, используя технику, описанную @jozefg, или загружена в ghci и запускается интерактивно (хотя и не в Windows - см. https://github.com/skogsbaer/HTF/issues/60 для получения более подробной информации).

Tasty - еще одна альтернатива, которая обеспечивает способ интеграции различных видов тестов. У него нет препроцессора, такого как HTF, но есть модуль, который выполняет аналогичные функции с использованием шаблона Haskell. Как и HTF, он также полагается на соглашение об именах для идентификации ваших тестов (в данном случае case_xxxx, а не test_xxxx). Помимо тестов HUnit и QuickCheck, в нем также есть модули для обработки нескольких других типов тестов.