是否有一些首选方法来设计Specs2测试,其中大量测试依赖于之前测试的结果?
下面,您将找到我当前的测试套件。我不喜欢测试片段之间的var
。然而,它们是“需要的”,因为一些测试会生成后续测试重用的ID号。
我是否应该在Specs2上下文中存储ID号,或者创建一个包含所有可变状态的单独Object?并且只在测试对象中放置测试片段?还是有一些更好的方法?
如果测试失败,我想取消相同深度的剩余测试。我可以让测试片段相互依赖吗? (我知道我可以在一个测试片段中取消剩余的匹配器(通过使用可变测试,或通过 orSkip ),但是取消整个片段呢?)
object DatabaseSpec extends Specification {
sequential
"The Data Access Object" should {
var someId = "" // These var:s feels error prone, is there a better way?
"save an object" >> {
someId = database.save(something)
someId must_!= ""
// I'd like to cancel the remaining tests, below, at this "depth",
// if this test fragmen fails. Can I do that?
// (That is, cancel "load one object", "list all objects", etc, below.)
}
"load one object" >> {
anObject = database.load(someId)
anObject.id must_== someId
}
"list all objects" >> {
objs = database.listAll()
objs.find(_.id == someId) must beSome
}
var anotherId = ""
...more tests that create another object, and
...use both `someId` and `anotherId`...
var aThirdId = ""
...tests that use `someId`, `anotherId` and `aThirdId...
}
"The Data Access Object can also" >> {
...more tests...
}
}
答案 0 :(得分:4)
您的问题分为两部分:使用vars存储中间状态,并在一个失败时停止示例。
1 - 使用vars
在使用可变规范时,有一些使用变量的替代方法。
您可以使用代表流程步骤的lazy vals
:
object DatabaseSpec extends mutable.Specification {
sequential
"The Data Access Object" should {
lazy val id1 = database.save(Entity(1))
lazy val loaded = database.load(id1)
lazy val list = database.list
"save an object" >> { id1 === 1 }
"load one object" >> { loaded.id === id1 }
"list all objects" >> { list === Seq(Entity(id1)) }
}
object database {
def save(e: Entity) = e.id
def load(id: Int) = Entity(id)
def list = Seq(Entity(1))
}
case class Entity(id: Int)
}
由于这些值是惰性的,因此只有在执行示例时才会调用它们。
如果您已准备好更改当前规范的结构,您还可以使用最新的1.12.3-SNAPSHOT并将所有这些小的期望分组到一个示例中:
"The Data Access Object provides a save/load/list api to the database" >> {
lazy val id1 = database.save(Entity(1))
lazy val loaded = database.load(id1)
lazy val list = database.list
"an object can be saved" ==> { id1 === 1 }
"an object can be loaded" ==> { loaded.id === id1 }
"the list of all objects can be retrieved" ==> {
list === Seq(Entity(id1))
}
}
如果这些期望中的任何一个失败,那么其余的将不会被执行,您将收到失败消息,如:
x The Data Access Object provides a save/load/list api to the database
an object can not be saved because '1' is not equal to '2' (DatabaseSpec.scala:16)
另一种可能需要2次小改进的可能性是使用Given/When/Then编写规范的方式,但在Given
和When
步骤中使用“抛出”期望。正如您在“用户指南”中看到的那样,Given/When/Then
步骤从字符串中提取数据并将输入的信息传递给下一个Given/When/Then
:
import org.specs2._
import specification._
import matcher.ThrownExpectations
class DatabaseSpec extends Specification with ThrownExpectations { def is =
"The Data Access Object should"^
"save an object" ^ save^
"load one object" ^ load^
"list all objects" ^ list^
end
val save: Given[Int] = groupAs(".*") and { (s: String) =>
database.save(Entity(1)) === 1
1
}
val load: When[Int, Int] = groupAs(".*") and { (id: Int) => (s: String) =>
val e = database.load(id)
e.id === 1
e.id
}
val list: Then[Int] = groupAs(".*") then { (id: Int) => (s: String) =>
val es = database.list
es must have size(1)
es.head.id === id
}
}
我要做的改进是:
groupAs(".*") and
的必要性。 在这种情况下,写下来应该足够了:
val save: Given[Int] = groupAs(".*") and { (s: String) =>
database.save(Entity(1)) === 1
1
}
另一种可能性是允许直接写:
val save: Given[Int] = groupAs(".*") and { (s: String) =>
database.save(Entity(1)) === 1
}
其中Given[T]
对象可以从String => MatchResult[T]
创建,因为MatchResult[T]
对象已经包含T
类型的值,这将成为“给定”。< / p>
2 - 在失败的示例后停止执行
使用隐式WhenFail
Around
上下文肯定是您想要的最佳方式(除非您按照G / W / T示例中所示的期望说明进行操作)。
有关step(stepOnFail = true)
如果上一个并发示例中的一个示例失败,则step(stepOnFail = true)
通过中断以下示例来工作。但是,当您使用sequential
时,前一个块仅限于一个示例。因此你所看到的。实际上我认为这是一个错误,所有剩下的例子都不应该执行,无论你是否使用顺序。所以请继续关注本周末即将到来的修复工作。
答案 1 :(得分:1)
(关于问题1:我不知道在示例中是否有更好的替代var
s。也许我的例子太长了,也许我应该把我的Spec:s分成许多小的规格。)
关于问题2,我发现in this email by etorreborre停止后续测试可以这样做:
"ex1" >> ok
"ex2" >> ok
"ex3" >> ko
step(stopOnFail=true)
"ex4" >> ok
(如果ex1,ex2或ex3失败,将跳过Ex4。(如果您使用的是顺序规范,这在Specs2&lt; 1.12.3中无法正常工作。)
这是另一种方式:根据this Specs2 Googl groups email by etorreborre,可以让后续测试在失败时停止,如下所示: (“example2”将被跳过,但“example3”和“4”将会运行)
class TestSpec extends SuperSpecification {
sequential
"system1" >> {
implicit val stop = WhenFail()
"example1" >> ko
"example2" >> ok
}
"system2" >> {
implicit val stop = WhenFail()
"example3" >> ok
"example4" >> ok
}
}
case class WhenFail() extends Around {
private var mustStop = false
def around[R <% Result](r: =>R) = {
if (mustStop) Skipped("one example failed")
else if (!r.isSuccess) { mustStop = true; r }
else r
}
}
在this email by etorreborre中有一种取消后续规范的方法,如果示例失败,如果您已包含规范列表:
sequential ^ stopOnFail ^
"These are the selenium specifications" ^
include(childSpec1, childSpec2, childSpec3)
并且您需要在build.sbt
中编辑测试选项,以便子项规范在包含后不再独立执行。来自电子邮件:
testOptions := Seq(Tests.Filter(s =>
Seq("Spec", "Selenium").exists(s.endsWith(_)) &&
! s.endsWith("ChildSpec")))
答案 2 :(得分:0)
Specs doc声明您可以使用.orSkip 在发生故障时跳过示例的其余部分
"The second example will be skipped" >> {
1 === 2
(1 === 3).orSkip
}
但我没有亲自尝试过