简体   繁体   English

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

[英]Testing custom data types which do not implement Eq

Lets say I have the following custom data type: 可以说我有以下自定义数据类型:

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

and some function 和一些功能

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

now I'm trying to write hspec tests for this like so: 现在我正试图为此编写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"]))]

this fails with: 这失败与:

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

My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal ? 我的问题是,是否可以在测试范围内暂时在Animal上实现Eq类型类? Or is there a better way to do this? 还是有更好的方法来做到这一点?

My question is, is it possible to temporarily, within the scope of the tests, implement the Eq typeclass on Animal? 我的问题是,是否可以在测试范围内暂时在Animal上实现Eq类型类?

Within the scope of the module, sure. 确保在模块范围内。 But if that module gets imported, you're going to leak the instance into other modules. 但是,如果导入了该模块,则将实例泄漏到其他模块中。 That's why creating instances is only recommended in the same module the data type has been defined, or where the class has been defined. 这就是为什么建议仅在已定义数据类型或已定义类的模块中使用创建实例的原因。 Otherwise you end up with orphan instances . 否则,您最终将成为孤立实例

Or is there a better way to do this? 还是有更好的方法来做到这一点?

Is it even remotely possible that the user want to compare characteristics? 用户是否想比较特性甚至有可能吗? Then derive Eq . 然后推导Eq It's the cleanest way. 这是最干净的方法。 Also, you're going to need a Show instance, so you're probably deriving something already: 另外,您将需要一个Show实例,因此您可能已经派生了一些东西:

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

If you cannot change the original source you can still use -XStandaloneDeriving to derive the instance in another module (see orphan instances above, though). 如果您不能更改原始源,则仍然可以使用-XStandaloneDeriving来在另一个模块中派生该实例(不过,请参见上面的孤立实例)。

However if you actually want to use some special Eq test you can either fiddle around with newtype wrappers, or simply write your own combinator: 但是,如果您实际上想使用一些特殊的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 (==)

It's a bit of a strange requirement. 这是一个奇怪的要求。 But you can just add the Eq instance inside your test. 但是您可以只在测试中添加Eq实例。 Haskell doesn't have a restriction on having the instance in the same source file or module as the data type or type class. Haskell没有将实例与数据类型或类型类放在同一源文件或模块中的限制。

If you wanted to derive the instance, rather than writing it yourself, you can (in GHC) use the extension StandaloneDeriving and write: 如果要派生实例,而不是自己编写实例,则可以(在GHC中)使用扩展StandaloneDeriving并编写:

deriving instance Eq Animal

Edit : Having said that, I can't see a good reason why you wouldn't just add the instance along with your main definition of Animal . 编辑 :话虽如此,我看不出为什么不将实例与您的Animal的主要定义一起添加的充分理由。 It won't do any harm, and it's pretty standard to add common typeclass derivations up front, just in case you need them later. 这不会造成任何危害,并且很常见的做法是预先添加常见的类型类派生类,以防万一您以后需要它们。

You should test observable behaviour, not implementation details. 您应该测试可观察的行为,而不是实现细节。 If, for whatever reason, whether two Animals are equal is not something that should be observable (and hence you don't want to implement Eq in your source code), then don't use that property in your test. 如果出于某种原因,两个动物是否相等不是应该观察到的(因此,您不想在源代码中实现Eq),则不要在测试中使用该属性。

So instead of testing that checkAnimalType returns a particular list of animals, focus on what you're supposed to be able to do with that list, and check that those properties hold. 因此,而不是测试是checkAnimalType返回动物对你应该怎样能够做到与该名单,并检查这些属性保留特定名单,重点关注。

Eg If you can get names out of them, check those. 例如,如果您可以从中获取姓名,请进行检查。 If it's supposed to be returning all the Animals that have some particular relationship to the input Animal, check that that relationship holds (and possibly that it doesn't hold for any other Animals). 如果应该返回与输入动物具有某些特定关系的所有动物,请检查该关系是否成立(并可能对其他任何动物都不成立)。 If Animal is not Eq, but you can canonically convert it to something that is (say if Animal holds some internal implementation gunk that's not Eq friendly, but you can serialize them or otherwise convert them to a more "just data" format), convert the output of checkAnimalType and then use the Eq of that. 如果Animal不是Eq,但是您可以将其规范地转换为某种东西(例如,Animal持有一些对Eq不友好的内部实现方式,但是您可以序列化它们,或者将它们转换为更“公正的数据”格式),请转换checkAnimalType的输出,然后使用其等式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM