Тестування одиниць Haskell


77

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

У деяких прикладах я бачу, як ви налаштовуєте тестові кейси, експортуєте їх у список тестів, а потім запускаєте у ghci за допомогою runTestsTT(як цей приклад HUnit ).

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

У будь-якому разі, хтось може допомогти мені це розібратися? Що вважається стандартним способом роботи у Хаскелі? Які найкращі практики? Що найпростіше встановити та підтримувати?


Ви переглядали бібліотеку QuickCheck? Мені завжди було досить просто у використанні.
bheklilr

4
Так, але швидка перевірка - це інший варіант використання, це тестування на основі типу, чого я зараз не хочу. Мені було б цікаво дізнатись, як це інтегрувати, хоча, як тільки я
обмотую

twitter.com/HaskellTips/status/425793151660331008 каже, що віддає перевагу tastyбільш test-framework(HTF?), але я також бачу, що HTF отримав невелике оновлення минулого тижня, після декількох місяців спокою.
misterbee

Відповіді:


48

Як правило, будь-який значний проект 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)

6
У міру зростання проекту ви повинні підтримувати експортований перелік тестів? Здається, схильні до помилок. Я думаю, я все ще збентежений щодо того, як це пов’язано з методом автоматичного експорту препроцесора? Я бачу багато прикладів модульного тестування, але все по-різному
devshorts

@devshorts У списку тестів можна назвати кожен тест окремо. Я вірю, що існують фреймворки, які автоматично запускатимуть ваші тести, але я зазвичай маю близько 10 тестів на файл, тому ведення таких невеликих списків досить просто.
Даніель Гратцер,

1
Чи можете ви пояснити, як ви використовуєте цей метод із декількома тестовими файлами? Чи основний бігун відокремлений або зазвичай є частиною випробувального приладу?
devshorts

1
@devshorts Я розділяю свої тести окремо і з кожного модуля експортую список тестів з їх іменами. Тоді в основному я поєдную списки та запускаю їх.
Даніель Гратцер,

Я використав цю відповідь для написання тестової основи для цього маленького проекту: github.com/siddharthist/m3u-convert Дякую @jozefg, і я сподіваюся, що приклад допоможе всім :-)
Ленгстон,

34

Я також новачок 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, тому я дійсно рекомендую прочитати запропонований посібник та бібліотечний посібник користувача .


1
Привіт, посилання на Початок роботи з HUnit порушено
сандвуд

4

Ви мали відповіді на більшість своїх запитань, але ви також запитували про HTF та як це працює.

HTF - це фреймворк, призначений як для модульного тестування - він сумісний із HUnit (він інтегрує та обертає його для надання додаткових функцій), так і для тестування на основі властивостей - він інтегрується за допомогою швидкої перевірки. Він використовує препроцесор для пошуку тестів, так що вам не доведеться вручну створювати список. Препроцесор додається до ваших тестових вихідних файлів за допомогою прагми:

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

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

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

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

Цю програму можна інтегрувати з cabal, використовуючи техніку, описану @jozefg, або завантажувати в ghci та запускати інтерактивно (хоча не у Windows - докладніше див. Https://github.com/skogsbaer/HTF/issues/60 ).

Смачне - ще одна альтернатива, яка забезпечує спосіб інтеграції різних видів тестів. Він не має попереднього процесора, як HTF, але має модуль, який виконує подібні функції за допомогою шаблону Haskell . Як HTF, він також спирається на іменування , щоб ідентифікувати тести (в даному випадку, case_xxxxа не test_xxxx). Окрім тестів HUnit та QuickCheck, він також має модулі для обробки ряду інших типів тестів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.