gpt4 book ai didi

haskell - 一套快速检查测试与实现相匹配是好事还是坏事?

转载 作者:行者123 更新时间:2023-12-02 10:52:57 25 4
gpt4 key购买 nike

我正在尝试开始使用 Haskell 的 QuickCheck,虽然我熟悉测试方法背后的概念,但这是我第一次尝试将其用于超出测试内容的项目,例如 反向。反向 == id 之类的事情。我想知道将其应用于业务逻辑是否有用(我认为很有可能)。

因此,我想测试的几个现有业务逻辑类型函数如下所示:

shouldDiscountProduct :: User -> Product -> Bool
shouldDiscountProduct user product =
if M.isNothing (userDiscountCode user)
then False
else if (productDiscount product) then True
else False

对于这个函数,我可以编写如下所示的 QuickCheck 规范:

data ShouldDiscountProductParams
= ShouldDiscountProductParams User Product

instance Show ShouldDiscountProductParams where
show (ShouldDiscountProductParams u p) =
"ShouldDiscountProductParams:\n\n" <>
"- " <> show u <> "\n\n" <>
"- " <> show p

instance Arbitrary ShouldDiscountProductParams where
arbitrary = ShouldDiscountProductParams <$> arbitrary <*> arbitrary

shouldDiscountProduct :: Spec
shouldDiscountProduct = it behavior (property verify)
where
behavior =
"when product elegible for discount\n"
<> " and user has discount code"

verify (ShouldDiscountProductParams p t) =
subject p t `shouldBe` expectation p t

subject =
SUT.shouldDiscountProduct

expectation User{..} Product{..} =
case (userDiscountCode, productDiscount) of
(Just _, Just _) -> True
_ -> False

我最终得到的是一个函数expectation,它验证了shouldDiscountProduct的当前实现,只是更加优雅。所以现在我有一个测试,我可以重构我原来的功能。但我的自然倾向是将其更改为期望中的实现:

shouldDiscountProduct User{..} Product{..} =
case (userDiscountCode, productDiscount) of
(Just _, Just _) -> True
_ -> False

但这没关系吧?如果我想将来再次更改此功能,我可以准备相同的功能来验证我的更改是否合适,并且不会无意中破坏某些内容。

或者这是矫枉过正/双重簿记吗?我想我在 OOP 测试中已经根深蒂固地认为,你应该尽可能避免镜像实现细节,这实际上不能比这更进一步,这就是实现!

然后我认为,当我浏览我的项目并添加这些类型的测试时,我实际上将添加这些测试,然后重构到我在 expectation 断言中实现的更清晰的实现。显然,对于比这些更复杂的函数来说,情况不会如此,但总的来说,我认为情况会是这样。

人们对于使用基于属性的测试来测试业务逻辑类型的功能有何体验?对于此类事情有什么好的资源吗?我想我只是想验证我是否以适当的方式使用 QC,这只是我过去的 OOP 对此产生了怀疑......

最佳答案

很抱歉几个月后才介入,但由于这个问题很容易在 Google 上出现,我认为它需要一个更好的答案。

当您谈论属性测试时,Ivan 的答案是关于单元测试的,所以让我们忽略它。

Dfeuer 会告诉您何时可以接受镜像实现,但不会告诉您针对您的用例应该做什么。

首先重写实现代码是基于属性的测试 (PBT) 的一个常见错误。但这不是 PBT 的目的。它们的存在是为了检查函数的属性。嘿,别担心,我们在前几次写 PBT 时都会犯这个错误:D

您可以在此处检查的一种属性是您的函数响应是否与其输入一致:

if SUT.shouldDiscountProduct p t 
then isJust (userDiscountCode p) && isJust (productDiscount t)
else isNothing (userDiscountCode p) || isNothing (productDiscount t)

这在您的特定用例中很微妙,但请注意,我们颠倒了逻辑。您的测试检查输入,并基于此对输出进行断言。我的测试检查输出,并基于此对输入进行断言。在其他用例中,这可能不太对称。大多数代码也可以重构,我让你这个练习;)

但您可能会发现其他类型的属性!例如。 不变性属性:

SUT.shouldDiscountProduct p{userDiscountCode = Nothing} t == False
SUT.shouldDiscountProduct p{productDiscount = Nothing} t == False

看看我们在这里做了什么?我们修复了输入的一部分(例如,用户折扣代码始终为空),并且我们断言无论其他一切如何变化,输出都是不变的(始终为假)。产品折扣也是如此。

最后一个例子:您可以使用类似属性来检查旧代码和新代码的行为是否完全相同:

shouldDiscountProduct user product =
if M.isNothing (userDiscountCode user)
then False
else if (productDiscount product) then True
else False

shouldDiscountProduct' user product
| Just _ <- userDiscountCode user
, Just _ <- productDiscount product
= True
| otherwise = False

SUT.shouldDiscountProduct p t = SUT.shouldDiscountProduct' p t

这意味着“无论输入如何,重写的函数必须始终返回与旧函数相同的值”。重构时这太酷了!

我希望这可以帮助您掌握基于属性的测试背后的想法:不要太担心函数返回的值,而开始想知道您的函数具有的一些行为。

请注意,PBT 并不是单元测试的敌人,它们实际上非常适合在一起。如果您对实际值感到更安全,您可以使用 1 或 2 个单元测试,然后编写属性测试来断言您的函数具有某些行为,无论输入如何。

关于haskell - 一套快速检查测试与实现相匹配是好事还是坏事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52433713/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com