Scala 如何设计Specs2数据库测试,以及相互依赖的测试?
有没有更好的方法来设计一个测试,有很多测试依赖于以前测试的结果 在下面,您可以找到我当前的测试套件。我不喜欢测试片段之间的Scala 如何设计Specs2数据库测试,以及相互依赖的测试?,scala,integration-testing,specs2,Scala,Integration Testing,Specs2,有没有更好的方法来设计一个测试,有很多测试依赖于以前测试的结果 在下面,您可以找到我当前的测试套件。我不喜欢测试片段之间的vars。不过,它们是“需要的”,因为有些测试会生成后续测试重用的ID号 我应该将ID号存储在Specs2上下文中,还是创建一个单独的对象来保存所有可变状态?并且只在规范对象中放置测试片段?还是有更好的方法 如果某个测试失败,我想取消相同深度的剩余测试。我可以使测试片段相互依赖吗?(我知道我可以取消单个测试片段中剩余的匹配器(通过使用可变测试,或通过orSkip),但是取消整
var
s。不过,它们是“需要的”,因为有些测试会生成后续测试重用的ID号
var
s替代方案。也许我的示例太长了,也许我应该将我的规范分成许多更小的规范。)
关于问题2,我发现停止后续测试可以这样做:
"ex1" >> ok
"ex2" >> ok
"ex3" >> ko
step(stopOnFail=true)
"ex4" >> ok
(如果ex1、ex2或ex3失败,则将跳过Ex4。(但是,如果使用的是顺序规范,则在规范2<1.12.3中无法按预期工作。)
这是另一种方法:根据我们的经验,可以让后续测试在失败时停止,如下所示: (“示例2”将被跳过,但“示例3”和“4”将运行) 您需要在
build.sbt
中编辑测试选项,这样子规范在包含之后就不会再次独立执行。通过电子邮件:
testOptions := Seq(Tests.Filter(s =>
Seq("Spec", "Selenium").exists(s.endsWith(_)) &&
! s.endsWith("ChildSpec")))
您的问题有两个部分:使用vars存储中间状态,以及在失败时停止示例 1-使用变量 在使用可变规范时,有一些替代使用vars的方法 您可以使用表示流程步骤的
惰性VAL
:
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)
另一种可能需要两个小的改进,即使用编写规范的方式,但在给定的和步骤中使用“抛出”期望。正如您在《用户指南》中所看到的,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(“.*”)和李>
在这种情况下,写下以下内容就足够了:
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
}
其中,由于MatchResult[T]
对象已包含类型为T
的值,因此可以从String=>MatchResult[T]
创建给定的[T]
对象,该对象将成为“给定”
2-在示例失败后停止执行
在上下文中使用隐式的WhenFail
当然是做你想做的事情的最佳方式(除非你按照上面G/W/T示例所示的期望描述)
关于步骤的注释(stepOnFail=true)
步骤(stepOnFail=true)
的工作原理是,如果上一个并发示例块中的一个示例失败,则中断以下示例。但是,当您使用顺序
时,前面的块仅限于一个示例。这就是你所看到的。实际上,我认为这是一个bug,不管您是否使用sequential,都不应该执行所有剩余的示例。因此,请继续关注本周末即将到来的修复。实际上,这只会取消当前{…}
块中的剩余测试(我认为这被称为“测试片段”)。我要寻找的是在同一深度上消除所有后续测试片段的东西(我的意思是,文本“第二个示例…”开始的“深度”,而不是{…}
块中的“深度”)。我将更新我的问题,使其更加清晰。使用带有1.12.3-SNAPSHOT的==>
将期望分组到一个示例中似乎很好。我认为生成的代码相当容易阅读。此外,将所有的惰性VAL放在测试代码之上,我认为代码更容易阅读我可能会在接下来的几个月里,在接下来的几个月里重写一点测试套件(当时已经发布了1.123),并考虑使用<代码>=> < />代码。(我还将它分成许多较小的测试套件,并包括它们。)感谢关于步骤(stepOnFail=true)
的注释。所有更改都在1.12.3-SNAPSHOT中:现在可以从给定的/When/Then步骤抛出失败,可以直接从使用完整描述字符串的函数创建给定步骤,即步骤(stopOnFail=true)在顺序规范中的行为与预期一致。顺便说一句,specs2 3.x的设计就是为了解决这个问题,您可以根据以前的测试结果创建任意测试。请参见此处:
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)
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
}
}
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
}