为什么Specs2以随机顺序运行这些“顺序”测试?

时间:2012-11-01 08:19:34

标签: scala integration-testing specs2 playframework-2.1

我正在尝试从规范迁移到Specs2的旧数据库测试套件。但是,Specs2以奇怪的顺序运行测试(从我的角度来看),这会破坏测试,因为它们会更改数据库状态,并运行某些代码两次。

在下面找到测试的简化版本。据我所知,测试应按此顺序运行:(因为我已指定顺序):
! 222! 333! 444
然而,实际发生的是,它们按此顺序执行:
! 333! 222! 444

以下是测试:

object IncludedSpec extends Specification {
  sequential
  println("sssstart")

  "IncludedSpec" should {

    println("..222")
    "context free block" >> {
      println("....! 222 the first test")
      1 === 1
    }

    var variableN = 0

    "block with context" >> {
      println("....333")
      "using one variable" >> {
        println("......! 333 doing some tests and setting variableN")
        variableN = 123
      }

      println("....444")
      "using it again" >> {
        println("......! 444 testing variableN")
        variableN === 123
      }
      println("....555")
    }
  }

  println("eeeend")
}

以下是println输出的全部内容:

sssstart
eeeend
sssstart
eeeend
..222
....333
......! 333 doing some tests and setting variableN
....444
....555
....! 222 the first test
......! 444 testing variableN

我的两个问题:

  1. 为什么! 222先不执行?

  2. sssstart eeeend输出两次怎么可能?规范是一个对象,不会创建两次?

  3. 奇怪的是,如果我从测试中删除副作用 - 即删除 variableN 并用ok替换测试实体 - 测试按正确的顺序运行。

    版本细节:我正在使用Paly Framework 2.1-SNAPSHOT(2012年10月28日版本203df0e)和Scala 2.10.0-RC1运行这些测试。我认为与Play捆绑的Specs2版本是版本1.12,因为inline方法可用,并且它已添加到1.12(-SNAPSHOT)中,请参阅https://github.com/etorreborre/specs2/issues/87并且没有后续版本的Specs2。

    (哦,如果你认为我应该完全改写测试,那么请看一下这个问题:How design a Specs2 database test, with interdependent tests?

2 个答案:

答案 0 :(得分:10)

最初,在specs2中,您可以创建以下内容:

1 - in的示例:"this is an example" in { 1 must_== 1 }

2 - >>的示例:"this is an example" >> { 1 must_== 1 }

3 - 包含>>

的示例块
"this system should" >> {
  "do something" >> ok
  "do something else" >> ok
}

重要的是in仅为示例保留,并接受任何可转换为Result的内容。另一方面,>>可以用于示例和示例组(以具有统一的嵌套样式),因此它接受ExampleResult类型的值。

现在,当您想要执行以下操作时,情况开始变得有点复杂:

1 - 使用foreach创建一组示例

"this group has 5 examples" in {
  (1 to 5) foreach { i => "example "+i >> ok }
}

2 - 使用foreach创建一组期望

"this example has 5 expectations" in {
  (1 to 5) foreach { i => i must_== i }
}

问题是,foreach的两个表达式都有Unit类型。但他们正在做两件完全不同的事情!第一个是构建示例,因此需要立即评估此表达式以构建Specification。第二个是创建Example的主体并将在稍后执行(或者如果示例被过滤掉,则可能永远不会执行)。使用相同的运算符>>的两件事情是行不通的。

因此决定>>(block: =>Unit)意味着“这会通过副作用构建一组示例”,in(expectations: =>Unit)意味着“这构建了Example的主体可能具有期望这将是副作用。

现在,当这更好地解释了为什么你会看到一个与你的印刷陈述有关的奇怪订单:

..222
....333
......! 333 doing some tests and setting variableN
....444
....555
首先打印

,因为它们包含在Unit类型的块中,被解释为示例组。

并且:

....! 222 the first test
......! 444 testing variableN
因此打印

是因为它们属于MatchResult[_]类型的块,也就是说,它们被视为示例的主体。

我同意这是令人困惑的,我希望这种解释可以为他们的原因提供一些看法。当然,另一个教训是“副作用是偷偷摸摸的,因为他们没有告诉你他们真正在做什么”。

所以一般的specs2提示总是以适当的值结束你的块(除非你使用foreach结构,如上面的例子所示)。例如,在执行变量赋值的块的末尾添加ok可以解决您的问题。

答案 1 :(得分:0)

(更新:这不适用于我真正相当庞大的Spec。它仍然以看似随机的顺序执行,Specs2似乎从中间的某个地方开始。 - 但是当我做了下面提到的替换时一个较小的Spec,可能有10或20个例子,它确实导致规范以正确的顺序执行。很奇怪

这是部分解决方法,但不是真正的答案:

在测试片段之前使用in代替>>。然后以正确的顺序执行测试。 (但sssstart eeeend仍然会发生两次。)

所以这可以按预期工作:(我用>>替换了某些in

object IncludedSpec extends Specification {
  sequential
  println("sssstart")

  "IncludedSpec" should {

    println("..222")
    "context free block" in {   // not >>
      println("....! 222 the first test")
      1 === 1
    }

    var variableN = 0

    "block with context" >> {
      println("....333")
      "using one variable" in {   // not >>
        println("......! 333 doing some tests and setting variableN")
        variableN = 123
      }

      println("....444")
      "using it again" in {   // not >>
        println("......! 444 testing variableN")
        variableN === 123
      }
      println("....555")
    }
  }

  println("eeeend")
}

下面我的疑惑:

1

我认为in>>之间的区别在于:
in只能出现在测试片段之前(即{... x must_== y ... }块)。但
>>可以在整个示例(使用嵌套的测试片段)之前和测试片段之前出现。

所以当我写>>时,Specs2并不直接知道会发生什么。我不知道为什么这会改变测试执行顺序。除非Specs2需要......在{ ...}块内执行一些代码才能找到答案?但Specs2不使用反射吗?这感觉很奇怪。

2

如果测试根本没有副作用(在这种情况下,不更新共享var)。或者,如果所有测试都有副作用。然后,据我所知,他们以正确的顺序执行。 然而,如果某些测试没有副作用,除了读取共享var,那么它们似乎在测试后执行写入var。 (如果您使用>>而不是in。)这也感觉很奇怪。无论如何,我想我很快就会开始将我的测试移植到Specs2并用>>替换in: - )