gpt4 book ai didi

unit-testing - 基于属性的测试会让你重复代码吗?

转载 作者:行者123 更新时间:2023-12-04 04:58:26 25 4
gpt4 key购买 nike

我正在尝试用基于属性的测试 (PBT) 替换一些旧的单元测试,concreteley 用 scalascalatest - scalacheck 但我认为问题更普遍.简化的情况是,如果我有一个要测试的方法:

 def upcaseReverse(s:String) = s.toUpperCase.reverse

通常,我会编写如下单元测试:

assertEquals("GNIRTS", upcaseReverse("string"))
assertEquals("", upcaseReverse(""))
// ... corner cases I could think of

因此,对于每个测试,我都会写出我期望的输出,没问题。现在,使用 PBT,它就像:

property("strings are reversed and upper-cased") {
forAll { (s: String) =>
assert ( upcaseReverse(s) == ???) //this is the problem right here!
}
}

当我尝试编写一个适用于所有 String 输入的测试时,我发现自己不得不在测试中再次编写该方法的逻辑。在这种情况下,测试看起来像:

   assert ( upcaseReverse(s) == s.toUpperCase.reverse) 

也就是说,我必须在测试中编写实现以确保输出正确。有办法解决这个问题吗?我是否误解了 PBT,我是否应该测试其他属性,例如:

  • “字符串的长度应与原始字符串的长度相同”
  • “字符串应包含原文的所有字符”
  • “字符串不应包含小写字符”...

这也是有道理的,但听起来很做作而且不太清楚。任何在 PBT 方面有更多经验的人都可以在这里阐明一些问题吗?

编辑 :根据@Eric 的消息来源,我找到了 this post ,这正是我的意思的一个示例(在 再次应用类别):测试方法 times in (F#) :

type Dollar(amount:int) =
member val Amount = amount
member this.Add add =
Dollar (amount + add)
member this.Times multiplier =
Dollar (amount * multiplier)
static member Create amount =
Dollar amount

作者最终编写了如下测试:

let ``create then times should be same as times then create`` start multiplier = 
let d0 = Dollar.Create start
let d1 = d0.Times(multiplier)
let d2 = Dollar.Create (start * multiplier) // This ones duplicates the code of Times!
d1 = d2

所以,为了测试那个方法,在测试中复制了该方法的代码。在这种情况下,一些事情就像乘法一样微不足道,但我认为它可以推断出更复杂的情况。

最佳答案

This presentation提供了一些关于您可以为您的代码编写的属性类型而无需复制它的线索。

一般来说,考虑一下当您将要测试的方法与该类上的其他方法组合在一起时会发生什么是很有用的:

  • 大小
  • ++
  • 反转
  • toUpperCase
  • 包含

例如:

  • upcaseReverse(y)++ upcaseReverse(x) == upcaseReverse(x++ y)

然后想想如果实现被破坏会破坏什么。如果出现以下情况,该属性是否会失败:

  1. 大小没有保留?
  2. 不是所有的字符都是大写的吗?
  3. 字符串没有正确反转?

1.实际上是由 3 暗示的。我认为上面的属性会因 3 而中断。但是它不会因 2 而中断(例如,如果根本没有大写)。我们可以增强它吗?关于:

  • upcaseReverse(y)++ x.reverse.toUpper == upcaseReverse(x++ y)

我认为这个没问题,但不相信我并运行测试!

无论如何,我希望你能明白:

  1. 与其他方法组合
  2. 看看是否存在似乎成立的等式(例如演示文稿中的“往返”或“幂等性”或“模型检查”)
  3. 检查密码错误时您的属性(property)是否会损坏

请注意,1. 和 2. 由名为 QuickSpec 的库实现和 3. 是 "mutation testing" .

附录

关于您的编辑:Times 操作只是 * 的包装,因此没有太多要测试的。然而,在更复杂的情况下,您可能需要检查操作:

  • 有一个 unit 元素
  • 具有联想性
  • 是可交换的
  • 是分布式的加法

如果这些属性中的任何一个失败,这将是一个很大的惊喜。如果您将这些属性编码为任何二元关系 T x T -> T 的通用属性,您应该能够在各种上下文中非常轻松地重用它们(请参阅 Scalaz Monoid“法则”)。

回到您的 upperCaseReverse 示例,我实际上会编写 2 个单独的属性:

 "upperCaseReverse must uppercase the string" >> forAll { s: String =>
upperCaseReverse(s).forall(_.isUpper)
}

"upperCaseReverse reverses the string regardless of case" >> forAll { s: String =>
upperCaseReverse(s).toLowerCase === s.reverse.toLowerCase
}

这不会重复代码并说明 2 个不同的东西,如果您的代码错误,它们可能会中断。

总而言之,我和你之前有过同样的问题,对此感到非常沮丧,但过了一段时间后,我发现越来越多的情况下我没有在属性中复制我的代码,尤其是当我开始思考

  • 将测试函数与其他函数组合(第一个属性中的.isUpper)
  • 将测试函数与更简单的计算“模型”(第二个属性中的“不分大小写”)进行比较

关于unit-testing - 基于属性的测试会让你重复代码吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30938441/

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