Haskell Pipes и тестирование с помощью HSpec

Я написал программу для проекта, использующего Pipes, который мне очень нравится! Однако я изо всех сил пытаюсь провести модульное тестирование своего кода.

У меня есть ряд функций типа Pipe In Out IO () (например), которые я хочу протестировать с помощью HSpec. Как я могу это сделать?

Например, предположим, что у меня есть этот домен:

data Person = Person String Int | Unknown deriving (Show, Eq)
data Classification = Friend | Foe | Undecided deriving Show

и эта труба:

classify :: Pipe Person (Person, Classification) IO ()
classify = do
    p@(Person name _) <- await
    case name of 
      "Alex" -> yield (p, Friend)
      "Bob" -> yield (p, Foe)
      _ -> yield (p, Undecided)

Я хотел бы написать спецификацию:

main = hspec $ do
  describe "readFileP" $ 
    it "yields all the lines of a file"
      pendingWith "How can I test this Pipe? :("

person Alex    schedule 27.08.2016    source источник
comment
Преобразуйте свой канал в производителя или эффект и используйте toListM или просто runEffect для материализации значений. Очевидно, вы должны решить, как создавать и передавать тестовые данные в канал.   -  person user2407038    schedule 27.08.2016
comment
runEffect просто даст мне m r, в данном случае это IO (). Не знаете, как это должно помочь?   -  person Alex    schedule 27.08.2016
comment
Не runEffect classify, а runEffect (giveDataToClassify classify) - как я уже сказал, ваш канал принимает входные данные, и вы должны решить, какой ввод, просто комбинируя свой канал соответствующим образом с каналом, который создает выходные данные, не требуя ввода ( Я думаю, что в pipes это Producer). Например, делает ли toListM $ mapM_ yield [ Person "Bob" 10, Person "June" 20 ] >-> classify то, что вы хотите? Обратите внимание, что тип \xs -> toListM $ mapM_ yield xs >-> classify — это [Person] -> IO [(Person, Classification)], который мне кажется формой, совместимой с HSpec.   -  person user2407038    schedule 28.08.2016
comment
Я понимаю, что мне нужно передать данные в трубу. Моя проблема заключается в том, что вызов runEffect находится в монаде IO, которая не может оценить Expectation, требуемую hspec it.   -  person Alex    schedule 29.08.2016
comment
Глядя на тип it, принимает аргумент Example a => a, и у вас есть экземпляр для Example Expectation (т.е. Example (IO ())), семантика которого генерация исключения HUnitFailure означает провал теста, а генерация исключения Result (кажется?) обозначает успех теста. Что (почти) соответствует всем требованиям. Мне кажется странным, что нет экземпляра Example a => Example (IO a) или даже Example (IO Result) - кажется, они могут быть вам полезны. Может быть, вам стоит попробовать написать их самостоятельно?   -  person user2407038    schedule 29.08.2016
comment
Я попробую. Очень признателен.   -  person Alex    schedule 29.08.2016


Ответы (2)


Вы можете использовать функции пакета temporary для создания временных файлов с ожидаемыми данными. , а затем проверьте, правильно ли данные считываются конвейером.

Между прочим, ваш Pipe использует readFile, который выполняет ленивый ввод-вывод. Ленивый ввод-вывод и потоковые библиотеки, такие как каналы, не очень хорошо сочетаются друг с другом, фактически последние существуют в основном как альтернатива первым!

Возможно, вместо этого вам следует использовать функции, выполняющие строгий ввод-вывод, такие как openFile и getLine.

Одна неприятность со строгим вводом-выводом заключается в том, что он заставляет вас более тщательно подходить к распределению ресурсов. Как обеспечить закрытие каждого дескриптора файла в конце или в случае ошибки? Один из возможных способов добиться этого — работать в монаде ResourceT IO, а не непосредственно в IO.

person danidiaz    schedule 27.08.2016
comment
Мой код не делает ничего подобного с таким вводом-выводом (на самом деле вызовы БД и т. д.). Концепция IO в этом вопросе предназначена только для демонстрационных целей, а не для цели вопроса. Я хочу знать, могу ли я протестировать свой код Pipe с помощью HSpec и как это сделать. - person Alex; 27.08.2016

Хитрость заключается в использовании преобразователя монад toListM from Pipes ListT .

import Pipes
import qualified Pipes.Prelude as P
import Test.Hspec

data Person = Person String Int | Unknown deriving (Show, Eq)
data Classification = Friend | Foe | Undecided deriving (Show, Eq)

classify :: Pipe Person (Person, Classification) IO ()
classify = do
  p@(Person name _) <- await
  case name of 
    "Alex" -> yield (p, Friend)
    "Bob" -> yield (p, Foe)
    _ -> yield (p, Undecided)

Тест с использованием преобразователя ListT для преобразования канала в ListT и проверки с использованием HSpec:

main = hspec $ do
  describe "classify" $ do
    it "correctly finds friends" $ do
      [(p, cl)] <- P.toListM $ each [Person "Alex" 31] >-> classify
      p `shouldBe` (Person "Alex" 31)
      cl `shouldBe` Friend

Обратите внимание, что вам не обязательно использовать each, это может быть простой производитель с вызовом yield.

person Alex    schedule 08.09.2016