我想说,在你想要做的场景中,混合使用 BeforeAndAfter 或 BeforeAndAfterAll 是减少重复的最直观的方法之一:“设置”->“运行 test1”->“设置”-> “运行 test2”,“设置”(大部分)相同。
假设我们有一个讨厌的、难以测试的Database:
object Database {
private var content: List[Int] = Nil
def add(value: Int) = content = value :: content
def remove(): Unit = content = if (content.nonEmpty) content.tail else Nil
def delete(): Unit = content = Nil
def get: Option[Int] = content.headOption
override def toString = content.toString()
}
它是一个单例(所以我们不能只为每个测试实例化一个新的Database)并且它是可变的(所以如果第一个测试改变了一些东西,它会影响第二个测试)。
显然,最好不要有这样的结构(例如,在本例中使用实现Database 的List 会更好),但假设我们不能简单地改变这个结构.
编辑:请注意,在这种情况下,不可能(至少我想不出办法)并行地运行对同一个单例实例进行变异的测试。
为了仍然能够对其进行测试,我们需要在运行每个测试之前有一个干净的状态。假设我们想为每个测试使用相同的值填充数据库,我们可以让我们的基本测试套件类扩展BeforeAndAfter。 注意:存在两个特征:BeforeAndAfter,它定义了在每个测试用例执行之前和之后运行的before和after,以及@987654333 @,不同之处在于它定义了在每个测试套件之前和之后运行的方法。
class RestAPITest extends FlatSpec with ShouldMatchers with BeforeAndAfter {
before {
Database.delete()
Database.add(4)
Database.add(2)
}
}
现在我们可以有一个测试套件ATest 扩展这个基类:
class ATest extends RestAPITest {
"The database" should "not be empty" in {
Database.get shouldBe defined
}
it should "contain at least two entries" in {
Database.remove()
Database.get shouldBe defined
}
it should "contain at most two entries" in {
Database.remove()
Database.remove()
Database.get should not be defined
}
}
在每个测试开始时,数据库包含两个值4 和2。我们现在可以让其他测试套件扩展这个基类:
class BTest extends RestAPITest {
"The contents of the database" should "add up to 6" in {
getAll.sum shouldBe 6
}
"After adding seven, the contents of the database" should "add up to 13" in {
Database.add(7)
getAll.sum shouldBe 13
}
def getAll: List[Int] = {
var result: List[Int] = Nil
var next = Database.get
while(next.isDefined){
result = next.get :: result
Database.remove()
next = Database.get
}
result
}
}
当然,我们也可以在常规方法中分解出通用功能,就像两个测试用例都使用的getAll 中所做的那样。
附录:
引用问题:
您如何保持测试套件的简短?
在我看来,测试代码与生产代码没有太大区别。如果它们不属于您已经拥有的特定类,则使用方法将常用功能分解并放入单独的特征中。
但是,如果您的生产代码要求测试始终执行同一段代码,那么您的生产代码中可能存在太多依赖项。假设你有一个函数(在你的生产代码中)
def plus: Int = {
val x = Database.get.get
Database.remove()
x + Database.get.get
}
那么你不能测试这个函数,除非你用你想要添加的两个值来填充你的数据库。在这种情况下,让你的测试更短、更易读的最好方法是重构你的生产代码。
"plus 3 2" should "be 5" in {
Database.add(3)
Database.add(2)
plus shouldBe 5
}
可能变成
"plus 3 2" should "be 5" in {
plus(3,2) shouldBe 5
}
在某些情况下,摆脱依赖并不容易。但是您可能希望测试场景中的对象依赖于特殊的测试环境。数据库就是一个很好的例子,文件系统或日志记录也是如此。这些事情的执行成本(I/O 访问)往往更高,并且可能具有您必须首先建立的进一步依赖关系。
在这些情况下,您的测试很可能会从使用模拟对象中获益。例如,您可能希望实现一个实现数据库接口的内存数据库。