简单的Haskell单元测试

53

我想要完成Haskell 99题,并且希望专注于解决方案但同时进行测试。如果我有第一个问题的解决方案,它是一个包含三行代码的.hs文件,

myLast :: [a] -> a
myLast [x] = x
myLast (_:xs) = myLast xs

我需要添加多少最少的代码才能在此基础上内联添加测试,并使用runhaskell运行它们?

2个回答

66

QuickCheck(主要为您生成测试输入)可能是测试纯函数的最佳方法。如果问题中的函数有来自标准库的模拟,则可以将标准函数用作模型来测试您的函数:

{-# LANGUAGE TemplateHaskell #-}

import Test.QuickCheck
import Test.QuickCheck.All

myLast :: [a] -> a
myLast [x] = x
myLast (_:xs) = myLast xs

-- here we specify that 'myLast' should return exactly the same result
-- as 'last' for any given 'xs'
prop_myLast xs = myLast xs == last xs


return [] -- need this for GHC 7.8
-- quickCheckAll generates test cases for all 'prop_*' properties
main = $(quickCheckAll)

如果你运行它,你会得到:

=== prop_myLast on tmp3.hs:12 ===
*** Failed! Exception: 'tmp3.hs:(7,1)-(8,25): Non-exhaustive patterns in function myLast' (after 1 test):  
[]
False
因为你的代码中的 myLast 没有处理 [] 的情况(实际上应该处理并抛出一个类似于 'last' 的错误)。但是在这里,我们可以通过指定只使用非空字符串(使用 ==> 组合符号)来简单地调整我们的测试用例:
prop_myLast xs = length xs > 0 ==> myLast xs == last xs

这将使得所有100个自动生成的测试用例都通过myLast

=== prop_myLast on tmp3.hs:11 ===
+++ OK, passed 100 tests.
True

PS另一种指定myLast行为的方法可能是:

prop_myLast2 x xs = myLast (xs++[x]) == x

或者更好的方式:

prop_myLast3 x xs = x `notElem` xs ==> myLast (xs++[x]) == x

1
当我使用 runhaskell lists.hs 运行我的文件时,它会给出 "lists.hs:44:8: parse error on input `$'" 的错误信息。这是我应该运行它的方式吗?https://gist.github.com/923814 - Daniel Huckstep
2
尝试按照我的示例添加 {-# LANGUAGE TemplateHaskell #-}。如果你的 runhaskell 指向 runghc,那么这应该可以解决问题。同时将 prop_last' 重命名为 prop_last - 否则 QuickCheck 似乎会忽略它。 - Ed'ka
2
所有的东西都可以正常工作(谢谢!),但现在我该如何让quickcheck捕获错误呢?我添加了last' [] = error "empty list" - Daniel Huckstep
最终我使用了QuickCheck的属性/过滤器(就像你说的那样),因为在我的搜索中,我不能告诉QuickCheck它应该期望一个错误,只能告诉它不要运行那些。 - Daniel Huckstep
1
这有点过时了。如我在这里的回答中所述,GHC 7.8需要在prop_quickCheckAll之间加入一行return [](或其他内容以打断TH处理)。 - Ørjan Johansen

2

hspec是Haskell的一个测试框架,受到Ruby RSpec的启发。它与QuickCheck、SmallCheck和HUnit集成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接