Wordspec "should" "when" "in"

Wordspec "should" "when" "in"

我正在为我的一个 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)
}

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

我在这里注意到的是,如果我试图在测试的 when 块中使用它,我的 SomeTestClassCompanionObject.someConfigurationINeed 字段没有被初始化,但是它 in 块中初始化。我的问题是:究竟是什么区分了 Wordspec 中的 shouldwhenin 范围?我的印象是这些只是逻辑上的差异,但这个测试表明不同的东西在 JVM 代码的底层 "static" 块中的不同时间被初始化。

是否有人有任何进一步的阅读或指向 Wordspec 文档的链接来解释这里发生的事情?

@BogdanVakulenko下面怎么设计

class SomeClassSpec {
  SomeTestClassCompanionObject.someConfigurationINeed // NullPointerException or WhosebugError 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
  }
}

因为尽管他们采用了 pass-by-name 参数 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 by-name

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

因为它会导致调用 registerTest,它只注册函数值 f 以供执行,但 f 在传递给注册时绝不会被求值。然后单独的 Runner 对象实际运行 f 但此时对 SomeTestClassCompanionObject.someConfigurationINeed 的调用是在 SomeClassSpec 的构造函数之外执行的,因此没有触发循环。