如何设计具有相互依赖的测试的Specs2数据库测试? [英] How design a Specs2 database test, with interdependent tests?
问题描述
是否有一些首选的方法来设计 Specs2 测试,其中许多测试取决于以前测试的结果?
Is there some preferred way to design a Specs2 test, with lots of tests that depend on the results of previous tests?
下面,您将找到我当前的测试套件.我不喜欢测试片段之间的var
.不过,它们是必需的",因为某些测试生成的ID号可供后续测试重用.
Below, you'll find my current test suite. I don't like the var
s inbetween the test fragments. They're "needed" though, since some tests generate ID numbers that subsequent tests reuses.
-
应该将ID号存储在Specs2上下文中,还是创建一个包含所有可变状态的单独Object?并仅将测试片段放置在规范对象中?还是有更好的方法?
Should I perhaps store the ID numbers in a Specs2 Context instead, or create a separate Object that holds all mutable state? And place only test fragments in the specification object? Or is there some even better approach?
如果测试失败,我想在相同深度取消其余测试.我可以使测试片段相互依赖吗? (我知道我可以取消单个测试片段中的剩余匹配项(通过使用可变测试或通过 orSkip ),但是取消整个片段又如何呢?)
If a test fails, I'd like to cancel the remaining test at the same depth. Can I make the test fragments depend upon each other? (I know I can cancel remaining matchers in a single test fragment (by using mutable tests, or via orSkip), but what about cancelling whole fragments?)
.
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...
}
}
推荐答案
您的问题有两个部分:使用vars存储中间状态,并在出现故障时停止示例.
There are 2 parts to your question: using vars for storing intermediary state, and stopping examples when one is failing.
1-使用vars
使用可变规范时,有一些使用var的替代方法.
There are some alternatives to using vars when using a mutable specification.
您可以使用lazy vals
代表过程的步骤:
You can use lazy vals
representing the steps of your process:
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)
}
由于这些值是惰性的,因此只有在执行示例时才会调用它们.
Since those values are lazy, they will only be called when the examples are executed.
如果您准备更改当前规范的结构,则还可以使用最新的1.12.3-SNAPSHOT并将所有这些小的期望归为一个示例:
If you're ready to change the structure of your current specification you can also use the latest 1.12.3-SNAPSHOT and group all those small expectations into one example:
"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))
}
}
如果这些期望中的任何一个失败,那么其余的将不会执行,并且您将收到类似以下的失败消息:
If any of those expectations fail then the rest will not be executed and you will get a failure message like:
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/Then
:
Another possibility, which would require 2 small improvements, would be to use the Given/When/Then way of writing specifications but using "thrown" expectations inside Given
and When
steps. As you can see in the User Guide, the Given/When/Then
steps extract data from strings and pass typed information to the next 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
.
在这种情况下,编写以下内容就足够了:
In that case it should be enough to write:
val save: Given[Int] = groupAs(".*") and { (s: String) =>
database.save(Entity(1)) === 1
1
}
另一种可能性是允许直接写:
Another possibility would be to allow to directly write:
val save: Given[Int] = groupAs(".*") and { (s: String) =>
database.save(Entity(1)) === 1
}
可以从String => MatchResult[T]
创建Given[T]
对象的原因,因为MatchResult[T]
对象已经包含类型为T
的值,该值将成为给定".
where a Given[T]
object can be created from a String => MatchResult[T]
because the MatchResult[T]
object already contains a value of type T
, that would become a "Given".
2-示例失败后停止执行
2 - Stop the execution after a failing example
使用隐式WhenFail
Around
上下文无疑是完成所需操作的最佳方法(除非您按照G/W/T示例上方的期望描述进行操作).
Using the implicit WhenFail
Around
context is certainly the best way to do what you want (unless you go with the expectations descriptions as shown above the G/W/T example).
关于step(stepOnFail = true)
如果在上一个并发示例块中的一个示例失败,则step(stepOnFail = true)
通过中断以下示例进行工作.但是,当您使用sequential
时,该上一个块仅限于一个示例.因此,您所看到的.实际上,我认为这是一个错误,无论您是否使用顺序,都不应执行其余所有示例.因此,请继续关注本周末即将发布的修复程序.
The step(stepOnFail = true)
works by interrupting the following examples if one example in the previous block of concurrent examples failed. However, when you're using sequential
, that previous block is limited to just one example. Hence what you're seeing. Actually I think that this is a bug and that all the remaining examples should not be executed, whether you're using sequential or not. So stay tuned for a fix coming up this week-end.
这篇关于如何设计具有相互依赖的测试的Specs2数据库测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!