Chi*_*rlo 3 unit-testing scalatest scalacheck property-based-testing
我正在尝试用基于属性的测试 (PBT) 替换一些旧的单元测试,具体地用scala和scalatest - scalacheck但我认为这个问题更普遍。简化的情况是,如果我有要测试的方法:
def upcaseReverse(s:String) = s.toUpperCase.reverse
Run Code Online (Sandbox Code Playgroud)
通常,我会编写单元测试,例如:
assertEquals("GNIRTS", upcaseReverse("string"))
assertEquals("", upcaseReverse(""))
// ... corner cases I could think of
Run Code Online (Sandbox Code Playgroud)
所以,对于每个测试,我写出我期望的输出,没问题。现在,使用 PBT,它会像:
property("strings are reversed and upper-cased") {
forAll { (s: String) =>
assert ( upcaseReverse(s) == ???) //this is the problem right here!
}
}
Run Code Online (Sandbox Code Playgroud)
当我尝试编写一个对所有String输入都适用的测试时,我发现自己不得不在测试中再次编写方法的逻辑。在这种情况下,测试将如下所示:
assert ( upcaseReverse(s) == s.toUpperCase.reverse)
Run Code Online (Sandbox Code Playgroud)
也就是说,我必须在测试中编写实现以确保输出正确。有没有办法解决这个问题?我是否误解了 PBT,我是否应该测试其他属性,例如:
这也是合理的,但听起来很做作,不太清楚。任何在 PBT 方面有更多经验的人都可以在这里解释一下吗?
编辑:按照@Eric 的消息来源,我找到了这篇文章,并且有一个我的意思的示例(在再次应用类别时):测试times( 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
Run Code Online (Sandbox Code Playgroud)
作者最终编写了一个测试,如下所示:
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
Run Code Online (Sandbox Code Playgroud)
因此,为了测试一个方法,该方法的代码在测试中被复制。在这种情况下,像乘法一样微不足道,但我认为它可以外推到更复杂的情况。
此演示文稿提供了一些有关您可以为代码编写的属性类型的线索,而无需复制它。
通常,考虑将要测试的方法与该类上的其他方法组合时会发生什么是有用的:
size++reversetoUpperCasecontains例如:
upcaseReverse(y) ++ upcaseReverse(x) == upcaseReverse(x ++ y) 然后想想如果实现被破坏会破坏什么。如果出现以下情况,财产是否会失败:
1. 实际上是由 3. 隐含的,我认为上面的属性会中断 3。但是它不会中断 2(例如,如果根本没有大写)。我们可以增强它吗?关于什么:
upcaseReverse(y) ++ x.reverse.toUpper == upcaseReverse(x ++ y) 我认为这个没问题,但不要相信我并运行测试!
无论如何,我希望你能明白:
请注意, 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
}
Run Code Online (Sandbox Code Playgroud)
这不会复制代码并说明 2 个不同的事情,如果您的代码错误,这些事情可能会中断。
总之,我以前和你有同样的问题,对此感到非常沮丧,但过了一段时间我发现越来越多的情况下我没有在属性中复制我的代码,尤其是当我开始考虑
.isUpper在第一个属性中)| 归档时间: |
|
| 查看次数: |
428 次 |
| 最近记录: |