我制作了一个创建“列表式”序列的库,其中实现了许多 Prelude 风格的函数。我想为此编写一些测试用例,以确保我的库产生正确的输出,我认为最简单的方法是编写一些函数,将结果转换为列表并将它们与 Prelude 结果进行比较。假设我们有这个:
import qualified MyLibrary as ML
import qualified Prelude as P
例如我可能想要以下测试用例:
P.take 5 (P.enumFrom 1) == toList (ML.take 5 (ML.enumFrom 1))
注意 ML.enumFrom
不输出列表,它输出它自己的数据类型。
上面的工作正常,但请注意我是如何“重复自己”(TM)。我必须确保左侧和右侧相同,否则我的测试用例是错误的。
有没有一种好的方法可以编写这样的测试用例,这样我就不必重复自己了?
最佳答案
第一个问题是P.take
和ML.take
等,只是看起来很像——实际上它们是完全不相关的函数,编译器不知道关于他们的共同行为的任何事情。因此,正如 @jd823592 所建议的,我们需要将它们与类型类分组(我使用了一个简单的 newtype
包装器,因此该示例是可编译的):
import Prelude hiding (take, enumFrom)
import qualified Prelude as P (take, enumFrom)
newtype MySeq a = MySeq [a]
class Sequence s where
take :: Int -> s a -> s a
enumFrom :: Enum a => a -> s a
toList :: s a -> [a]
instance Sequence MySeq where
take n (MySeq xs) = MySeq (P.take n xs)
enumFrom n = MySeq (P.enumFrom n)
toList (MySeq xs) = xs
instance Sequence [] where
take = P.take
enumFrom = P.enumFrom
toList = id
然后我们将尝试使用类定义中现在统一的函数来定义一些测试。它们可能只生成任何类型的 Sequence
,然后我们将强制它们生成显式类型。
test1 = doTest (take 5 $ enumFrom 1) -- the part in brackets is polymorphic
doTest :: (Eq a, Sequence s) => s a -> Bool
doTest test = ???
现在第二个问题是,我们传递一个多态函数作为参数,然后需要用不同类型的参数([a]
和 MySeq a
来实例化它案件)。在标准的 Haskell 2010 中这是不可能的,但我们可以利用 Rank2 (or RankN) extension :
{-# LANGUAGE Rank2Types #-}
<...>
doTest :: forall a . Eq a => (forall s . Sequence s => s a) -> Bool
doTest test = (test `asTypeOf` dummy1) == toList (test `asTypeOf` dummy2) where
dummy1 :: Eq a => [a]
dummy1 = undefined
dummy2 :: Eq a => MySeq a
dummy2 = undefined
这个解决方案有点笨拙,但仍然有效。请随时改进。
关于unit-testing - 在 Haskell 中为不同类型编写测试用例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42346429/