测试不实现 Eq 的自定义数据类型
Testing custom data types which do not implement Eq
假设我有以下自定义数据类型:
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 类型类?或者有更好的方法吗?
这个要求有点奇怪。但是您可以在测试中添加 Eq
实例。 Haskell 不限制实例与数据类型或类型 class.
在同一源文件或模块中
如果您想派生实例,而不是自己编写,您可以(在 GHC 中)使用扩展名 StandaloneDeriving
并编写:
deriving instance Eq Animal
编辑:话虽如此,我看不出有什么好的理由说明为什么您不将实例连同 Animal
的主要定义一起添加。它不会造成任何伤害,并且预先添加通用类型 class 派生是非常标准的,以防您以后需要它们。
My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal?
在模块范围内,当然可以。但是,如果该模块被导入,您将将该实例泄漏到其他模块中。这就是为什么仅建议在已定义数据类型或已定义 class 的同一模块中创建实例。否则你会得到 orphan instances.
Or is there a better way to do this?
用户是否有可能想要比较特征?然后导出Eq
。这是最干净的方法。此外,您将需要一个 Show
实例,因此您可能已经派生了一些东西:
data Animal = Characteristics [Char] (Set.Set [Char]) deriving (Show, Eq)
如果您无法更改原始来源,您仍然可以使用 -XStandaloneDeriving
在另一个模块中派生实例(不过请参阅上面的孤立实例)。
然而,如果你真的想使用一些特殊的 Eq
测试,你可以 fiddle 使用 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 (==)
您应该测试可观察到的行为,而不是实现细节。如果出于某种原因,两个 Animals 是否相等不应该是可观察的(因此您不想在源代码中实现 Eq),则不要在测试中使用 属性。
因此,与其测试 checkAnimalType returns 特定的动物列表,不如关注你应该能够对该列表做 的事情,然后检查这些属性成立。
例如如果你能从他们那里得到名字,检查那些。如果它应该返回与输入动物有某种特定关系的所有动物,请检查该关系是否成立(并且可能它不适用于任何其他动物)。如果 Animal 不是 Eq,但您可以规范地将其转换为某种东西(比如 Animal 是否包含一些对 Eq 不友好的内部实现垃圾,但您可以将它们序列化或以其他方式将它们转换为更 "just data" 的格式),转换 checkAnimalType 的输出,然后使用它的 Eq。
假设我有以下自定义数据类型:
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 类型类?或者有更好的方法吗?
这个要求有点奇怪。但是您可以在测试中添加 Eq
实例。 Haskell 不限制实例与数据类型或类型 class.
如果您想派生实例,而不是自己编写,您可以(在 GHC 中)使用扩展名 StandaloneDeriving
并编写:
deriving instance Eq Animal
编辑:话虽如此,我看不出有什么好的理由说明为什么您不将实例连同 Animal
的主要定义一起添加。它不会造成任何伤害,并且预先添加通用类型 class 派生是非常标准的,以防您以后需要它们。
My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal?
在模块范围内,当然可以。但是,如果该模块被导入,您将将该实例泄漏到其他模块中。这就是为什么仅建议在已定义数据类型或已定义 class 的同一模块中创建实例。否则你会得到 orphan instances.
Or is there a better way to do this?
用户是否有可能想要比较特征?然后导出Eq
。这是最干净的方法。此外,您将需要一个 Show
实例,因此您可能已经派生了一些东西:
data Animal = Characteristics [Char] (Set.Set [Char]) deriving (Show, Eq)
如果您无法更改原始来源,您仍然可以使用 -XStandaloneDeriving
在另一个模块中派生实例(不过请参阅上面的孤立实例)。
然而,如果你真的想使用一些特殊的 Eq
测试,你可以 fiddle 使用 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 (==)
您应该测试可观察到的行为,而不是实现细节。如果出于某种原因,两个 Animals 是否相等不应该是可观察的(因此您不想在源代码中实现 Eq),则不要在测试中使用 属性。
因此,与其测试 checkAnimalType returns 特定的动物列表,不如关注你应该能够对该列表做 的事情,然后检查这些属性成立。
例如如果你能从他们那里得到名字,检查那些。如果它应该返回与输入动物有某种特定关系的所有动物,请检查该关系是否成立(并且可能它不适用于任何其他动物)。如果 Animal 不是 Eq,但您可以规范地将其转换为某种东西(比如 Animal 是否包含一些对 Eq 不友好的内部实现垃圾,但您可以将它们序列化或以其他方式将它们转换为更 "just data" 的格式),转换 checkAnimalType 的输出,然后使用它的 Eq。