Тестирование Haskell
Я новичок в haskell и работаю над модульным тестированием, однако считаю, что экосистема очень запутанна. Я смущен относительно отношений между HTF и HUnit.
В некоторых примерах я вижу, что вы настраиваете тестовые примеры, экспортируете их в список тестов, а затем запускаете в ghci с помощью runTestsTT
(например этот пример HUnit).
В других примерах вы создаете тестовый бегун, привязанный к кабальному файлу, который использует некоторую магию препроцессора, чтобы найти ваши тесты, как в этом git example. Также кажется, что HTF-тесты должны иметь префикс с test_
или они не запускаются? Мне было трудно найти документацию по этому поводу, я просто заметил шаблон, который у всех был.
В любом случае, может кто-то поможет разобраться в этом? Что считается стандартным способом делать вещи в Haskell? Каковы наилучшие методы? Что проще всего настроить и поддерживать?
Ответы
Ответ 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, в нем также есть модули для обработки нескольких других типов тестов.