首页 > 解决方案 > Wordspec “应该” “何时” “在”

问题描述

我正在为我的一个 scala 测试套件初始化一个伴随对象。此伴随对象中的一个字段是惰性求值的,并使用测试套件中的一些字段进行初始化。就像是:

class SomeClassSpec extends WordSpec with Matchers with OneInstancePerTest {
    lazy val someFieldTheCompanionObjectNeeds = "some_field_I_need"
    "my test class" should {
        "do something interesting" when {
            "I tell it to" in {
                //a bunch of test code using the SomeTestClassCompanionObject.someConfigurationINeed field.
            }
        }
    }
}

object SomeTestClassCompanionObject extends SomeClassSpec {
    lazy val someConfigurationINeed = Config(SomeTestClass.someFieldTheCompanionObjectNeeds)
}

不要问。我知道这是不好的做法,但必须这样做,这在很大程度上与我的问题无关。

我在这里注意到的是,SomeTestClassCompanionObject.someConfigurationINeed如果我尝试when在测试块内使用它,我的字段没有初始化,但是它在块内初始化in。我的问题是:真正区分每个should, when,in Wordspec 中我的印象是这些只是逻辑上的区别,但是这个测试表明,在 JVM 代码的底层“静态”块中,不同的东西在不同的时间被初始化。

有没有人有任何进一步的阅读或链接到 Wordspec 文档来解释这里发生了什么?

标签: scalalazy-loadinglazy-evaluationscalatestlazy-initialization

解决方案


@BogdanVakulenko展示了以下设计

class SomeClassSpec {
  SomeTestClassCompanionObject.someConfigurationINeed // NullPointerException or StackOverflowError because calling child's constructor which in turn calls parent's constructor
}

object SomeTestClassCompanionObject extends SomeClassSpec {
  lazy val someConfigurationINeed = ??
}

失败,因为从父构造函数调用子构造函数会导致循环。这种情况发生在shouldwhen

class SomeClassSpec {
  "my test class" should { 
    SomeTestClassCompanionObject.someConfigurationINeed // error
  }

  "do something interesting" when {
    SomeTestClassCompanionObject.someConfigurationINeed // error
  }
}

因为尽管他们采用了f仅在使用时才评估的按名称传递参数

def should(right: => Unit)
def when(f: => Unit)

它们导致调用registerNestedBranch确实会评估f从而触发循环

  def registerNestedBranch(description: String, childPrefix: Option[String], fun: => Unit, registrationClosedMessageFun: => String, sourceFile: String, methodName: String, stackDepth: Int, adjustment: Int, location: Option[Location], pos: Option[source.Position]): Unit = {
    ... 
    try {
      fun // Execute the function
    }
    ...
}

另一方面,循环不会发生in

class SomeClassSpec {
  "I tell it to" in {
    SomeTestClassCompanionObject.someConfigurationINeed // ok
  }
}

这也需要f按名

def in(f: => Any /* Assertion */)

因为它导致调用registerTest只注册函数值f以供执行但在任何时候都不会f被评估,因为它被传递下来进行注册。然后单独的Runner对象实际运行f,但此时调用在 的构造函数SomeTestClassCompanionObject.someConfigurationINeed之外执行SomeClassSpec,因此不会触发循环。


推荐阅读