Haskell中类型类实例实现的通用单元测试模式

时间:2017-05-05 12:48:01

标签: haskell hunit

我想知道是否存在编写通用单元测试代码的已知模式,其目的是检查(作为黑盒子)类型类的各种实例(实现)。例如:

import Test.HUnit

class M a where
foo :: a -> String
cons :: Int -> a     -- some constructor

data A = A Int
data B = B Int

instance M A where
  foo _ = "foo"
  cons  = A

instance M B where
  foo _ = "bar"     -- implementation error
  cons  = B 

我想编写一个函数tests,返回一个Test,以某种方式指定代码适用的特定实例tests。我在考虑使用默认实现将tests添加到类的定义中(忽略测试代码和实际代码之间的耦合问题),但我不能简单地使用tests :: Test,即使我尝试tests:: a -> Test(因此必须人为地传递给定类型的具体元素来调用函数),我无法弄清楚如何在代码中引用consfoo(类型)像(cons 0) :: a这样的注释不会这样做。

假设我改为class (Eq a) => M a where ...,类型AB导出Eq,我可以用类似的东西欺骗编译器(添加到{{1的定义中) }}):

M

但这对我来说非常难看。任何建议都热烈欢迎

1 个答案:

答案 0 :(得分:4)

代理

当前最常见的在内部"内部使函数多态的方法。 type是传递ProxyProxy有一个像()这样的无效构造函数,但它的类型带有幻像类型。这避免了必须传递undefined或虚拟值。然后可以将Data.Proxy.asProxyTypeOf用作注释。

tests :: M a => Proxy a -> Test
tests a = TestCase (assertEqual "foo" (foo (cons 0 `asProxyTypeOf` a)) "foo")

代理

我们也可以推广该类型,因为实际上并不需要Proxy作为值。它只是一种使类型变量不模糊的方法。您需要重新定义asProxyTypeOf。与前一个相比,这主要是风格问题。能够使用更多值作为潜在代理可以使一些代码更简洁,有时以可读性为代价。

-- proxy is a type variable of kind * -> *
tests :: M a => proxy a -> Test
tests a = TestCase (assertEqual "foo" (foo (cons 0 `asProxyTypeOf` a)) "foo")
  where
    asProxyTypeOf :: a -> proxy a -> a
    asProxyTypeOf = const

范围类型变量

函数asProxyTypeOf或您的(==)技巧实际上是无法从签名中引用类型变量的产物。事实上ScopedTypeVariables + RankNTypes扩展名允许这样做。

显式量化将变量a带入函数体中的范围。

tests :: forall a proxy. M a => proxy a -> Test
tests _ = TestCase (assertEqual "foo" (foo (cons 0 :: a)) "foo")  -- the "a" bound by the top-level signature.

如果没有ScopedTypeVariables扩展名,则cons 0 :: a会被解释为cons 0 :: forall a. a

以下是您使用这些功能的方法:

main = runTestTT $ TestList
  [ tests (Proxy :: Proxy A)
  , tests (Proxy :: Proxy B)
  ]

键入应用程序

自GHC 8以来,AllowAmbiguousTypes + TypeApplications扩展名不再需要Proxy参数。

tests :: forall a. M a => Test
tests = TestCase (assertEqual "foo" (foo (cons 0 :: a)) "foo")  -- the "a" bound by the top-level signature.

main = runTestTT $ TestList
  [ tests @A
  , tests @B
  ]
相关问题