1

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

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’

我的问题是,是否可以在测试范围内临时实现 Eq 类型类Animal?还是有更好的方法来做到这一点?

4

3 回答 3

3

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

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

还是有更好的方法来做到这一点?

用户是否有可能想要比较特征?然后导出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 (==)
于 2016-01-17T09:55:11.177 回答
2

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

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

deriving instance Eq Animal

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

于 2016-01-17T01:16:11.183 回答
2

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

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

例如,如果你能从中得到名字,检查那些。如果它应该返回与输入 Animal 有某种特定关系的所有 Animals,请检查该关系是否成立(并且可能对于任何其他 Animals 不成立)。如果 Animal 不是 Eq,但您可以将其规范地转换为某种东西(例如,如果 Animal 拥有一些对 Eq 不友好的内部实现 gunk,但您可以将它们序列化或以其他方式将它们转换为更“公正的数据”格式),转换checkAnimalType 的输出,然后使用它的 Eq。

于 2016-01-18T02:53:34.287 回答