scala - 使用 ScalaCheck / ScalaTest 子句时令人困惑的单元测试代码执行顺序
问题描述
在使用变量对类进行单元测试时,我面临以下令人困惑的行为。
为了一个简单的例子,假设我有以下类:
// Case classes are not an alternative in my use case.
final class C(var i: Int = 0) {
def add(that: C): Unit = {
i += that.i
}
override def toString: String = {
s"C($i)"
}
}
为此,我编写了以下琐碎且看似无害的单元测试:
import org.junit.runner.RunWith
import org.scalacheck.Gen
import org.scalatest.junit.JUnitRunner
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{MustMatchers, WordSpec}
@RunWith(classOf[JUnitRunner])
class CUnitTest extends WordSpec with MustMatchers with GeneratorDrivenPropertyChecks {
private val c: C = new C()
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
val expectedI = c.i + x.i
c.add(x)
s"result in its .i property becoming $expectedI" in {
c.i mustBe expectedI
}
}
}
}
除最后一个测试用例外,所有测试用例均失败:
例如,前三个测试用例失败,结果如下:
org.scalatest.exceptions.TestFailedException: 414 was not equal to 68
org.scalatest.exceptions.TestFailedException: 414 was not equal to 89
org.scalatest.exceptions.TestFailedException: 414 was not equal to 151
现在,围绕单元测试并移动子句c.add(x)
内的部分:in
import org.junit.runner.RunWith
import org.scalacheck.Gen
import org.scalatest.junit.JUnitRunner
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{MustMatchers, WordSpec}
@RunWith(classOf[JUnitRunner])
class CUnitTest extends WordSpec with MustMatchers with GeneratorDrivenPropertyChecks {
private val c: C = new C()
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
val expectedI = c.i + x.i
s"result in its .i property becoming $expectedI" in {
c.add(x)
c.i mustBe expectedI
}
}
}
}
除第一个测试用例外,所有测试用例均失败:
例如,第二个和第三个测试用例失败并显示以下消息:
org.scalatest.exceptions.TestFailedException: 46 was not equal to 44
org.scalatest.exceptions.TestFailedException: 114 was not equal to 68
此外,c.i
测试用例描述似乎并没有像我预期的那样增加。
显然,ScalaTest 子句中的执行顺序不是自上而下的。某些事情发生的时间早于或晚于它的编写顺序,或者可能根本不发生,具体取决于它在哪个子句中,但我无法理解它。
发生了什么事,为什么?此外,我怎样才能实现所需的行为(c.i
增加,所有测试用例都通过)?
解决方案
考虑像这样重写测试
import org.scalacheck.Gen
import org.scalatest._
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
class HelloSpec extends WordSpec with MustMatchers with ScalaCheckDrivenPropertyChecks {
private val c: C = new C()
"class C" must {
"add another class C" in {
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
val expectedI = c.i + x.i
c.add(x)
c.i mustBe expectedI
}
}
}
}
请注意这里forAll
是如何在测试主体的“内部”,这意味着我们有一个测试,它使用由提供的多个输入forAll
来测试系统C
。当它像这样在“外面”时
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
...
s"result in its .i property becoming $expectedI" in {
...
}
}
}
thenforAll
被误用于生成多个测试,其中每个测试都有一个测试输入,但是其目的是为被测系统forAll
生成多个输入,而不是多个测试。此外,CUnitTest
后续测试中的结果设计取决于先前测试的状态,这是错误且难以维护的。理想情况下,测试将彼此隔离运行,其中所有需要的状态都作为测试夹具的一部分重新提供。
一些旁注:@RunWith(classOf[JUnitRunner])
应该不是必需的,并且GeneratorDrivenPropertyChecks
已被弃用。
推荐阅读
- android - MotionLayout issue with Buttons, EditTexts and focus
- kotlin - Enforcing JDK package usage in kotlinc
- django - Django - 努力将输入数据提交到数据库
- node.js - 如何使用 momentjs 找到 now-1w/w 的开始和结束日期
- angular - 如何模拟不是注入服务而是导入命名空间的依赖项?
- spring - Spring Kafka - 并发性的增加会产生更多的重复
- c# - 如何根据linq中的两个属性过滤数据
- jquery - 使用 AJAX 时 Django 会话变量不起作用
- javascript - Photoswipe(移动) - 图像选择(photoswipe 打开)不时停止工作
- reactjs - 如何从 Api 在 React 中下载 Pdf 文件