假设我有以下自定义数据类型:
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类型类?或者有更好的方法吗?
答案 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。