测试未实现Eq的自定义数据类型

时间:2016-01-17 00:05:32

标签: haskell types typeclass assertions hspec

假设我有以下自定义数据类型:

data Animal = Characteristics [Char] (Set.Set [Char])

和一些功能

checkAnimalType :: [Char] -> Animal -> [Animal]

现在我正试图为此编写hspec测试:

describe "checkAnimalType" $ do
      it "returns a list of animals" $ do
        (checkAnimalType ["foo", "coo", "doo", "bar", "moo"](Characteristics "foo" $ Set.fromList(["foo", "coo"]))) $ `shouldBe` [(Characteristics "foo" $ Set.fromList(["cockadoodledoo"]))]

这失败了:

No instance for (Eq Animal) arising from a use of ‘shouldBe’

我的问题是,是否有可能在测试范围内暂时在Animal上实现Eq类型类?或者有更好的方法吗?

3 个答案:

答案 0 :(得分:3)

  

我的问题是,是否有可能在测试范围内暂时在Animal上实现Eq类型类?

在模块的范围内,当然。但是如果导入该模块,您将把实例泄漏到其他模块中。这就是为什么创建实例仅建议在已定义数据类型的同一模块中,或者已定义类的位置。否则,您最终会得到orphan instances

  

或者有更好的方法吗?

用户是否想远程比较特征?然后导出Eq。这是最干净的方式。此外,您将需要一个Show实例,因此您可能已经得到了一些东西:

data Animal = Characteristics [Char] (Set.Set [Char]) deriving (Show, Eq)

如果您无法更改原始源,您仍然可以使用-XStandaloneDeriving在另一个模块中派生实例(请参阅上面的孤立实例)。

但是,如果你真的想要使用一些特殊的Eq测试,你可以使用newtype包装器,或者只是编写自己的组合器:

-- newtype variant
newtype TAnimal = TAnimal Animal
instance Eq TAnimal where ...
instance Show TAnimal where...

animalShouldBe :: Animal -> Animal -> Expectation
animalShouldBe = shouldBe `on` TAnimal

-- custom operator variant
withShouldBe :: (Show a) => (a -> a -> Bool) -> a -> a -> Expectation
withShouldBe f a e = unless (f a e) $ expectationFailure msg
 where msg = "expected: " ++ show e ++ ", but got " ++ show a

 animalShouldBe = withShouldBe animalEqualityTest

-- Fun fact: shouldBe = withShouldBe (==)

答案 1 :(得分:2)

这有点奇怪的要求。但您可以在测试中添加Sort-Object实例。 Haskell没有限制将实例放在与数据类型或类型类相同的源文件或模块中。

如果你想派生实例,而不是自己编写,你可以(在GHC中)使用扩展名Eq并写:

StandaloneDeriving

编辑:话虽如此,我无法找到一个很好的理由说明为什么您不会将实例与deriving instance Eq Animal 的主要定义一起添加。它不会造成任何伤害,并且预先添加常见的类型类派生是非常标准的,以防万一你以后需要它们。

答案 2 :(得分:2)

您应该测试可观察的行为,而不是实现细节。无论出于何种原因,如果两个动物是否相等则不是可观察的东西(因此您不想在源代码中实现Eq),那么不要在测试中使用该属性。

因此,不是测试checkAnimalType返回特定的动物列表,而是关注您应该能够该列表的内容,并检查这些属性是否成立。

E.g。如果您可以从中获取名称,请检查它们。如果它应该返回与输入Animal有某种特定关系的所有动物,请检查该关系是否成立(并且可能它不适用于任何其他动物)。如果Animal不是Eq,但你可以将它正则地转换为某种东西(比如说Animal是否拥有一些不是Eq友好的内部实现gunk,但你可以将它们序列化或以其他方式将它们转换为更“仅数据”的格式),转换checkAnimalType的输出然后使用它的Eq。