Specs2 整套前后设置环境

Specs2 setting up environment before and after the whole suit

我正在为使用 dynamodb 的 spray.io 项目编写一些 specc2 集成测试。我正在使用 sbt-dynamodb 将本地 dynamodb 加载到环境中。在测试 运行 之前,我使用以下模式加载我的 tables。

trait DynamoDBSpec extends SpecificationLike {

  val config = ConfigFactory.load()

  val client = new AmazonDynamoDBClient(new DefaultAWSCredentialsProviderChain())

  lazy val db = {
    client.setEndpoint(config.getString("campaigns.db.endpoint"))
    new DynamoDB(client)
  }


  override def map(fs: =>Fragments): Fragments =
    Step(beforeAll) ^ fs ^ Step(afterAll)

  protected def beforeAll() = {
    //load my tables
  }

  protected def afterAll() = {
    //delete my tables
  }
}

然后任何测试 class 都可以使用 DynamoDBSpec 进行扩展,并且 table 将被创建。一切正常,直到从多个测试 class 扩展 DynamoDBSpec,在此期间它抛出 ResourceInUseException:'Cannot create preexisting table'。原因是它们是并行执行的,所以要同时执行table个创建。

我试图通过 运行在顺序模式下进行测试来克服它,但是 beforeall 和 afterall 仍然是并行执行的。

理想情况下,我认为最好在整个套件 运行 之前创建 table,而不是每个 Spec class 调用,然后在整个之后将其拆除套件完成。有谁知道如何做到这一点?

有两种方法可以实现。

有对象

您可以使用对象来同步数据库的创建

object Database {
  lazy val config = ConfigFactory.load()

  lazy val client = 
    new AmazonDynamoDBClient(new DefaultAWSCredentialsProviderChain())

  // this will only be done once in
  // the same jvm
  lazy val db = {
    client.setEndpoint(config.getString("campaigns.db.endpoint"))
    val database = new DynamoDB(client)
    // drop previous tables if any 
    // and create new tables
    database.create...
    database
  }
}

// BeforeAll is a new trait in specs2 3.x
trait DynamoDBSpec extends SpecificationLike with BeforeAll {
  //load my tables
  def beforeAll = Database.db
}

如你所见,在这个模型中,当规范完成时我们不会删除表(因为我们不知道是否所有其他规范都已执行),我们只是在重新运行 规格。这实际上可能是一件好事,因为这将帮助您调查故障(如果有)。

在全球范围内同步规范并在最后正确清理的另一种方法是使用规范链接。

有链接

使用 specs2 3.3,您可以在使用 links 的规范之间创建依赖关系。这意味着您可以定义一个 "Suite" 规范,它将:

  1. 启动数据库
  2. 收集所有相关规格
  3. 删除数据库

例如

import org.specs2._
import specification._
import core.Fragments
import runner.SpecificationsFinder

// run this specification with `all` to execute
// all linked specifications
class Database extends Specification { def is =
  "All database specifications".title ^ br ^
  link(new Create).hide ^
  Fragments.foreach(specs)(s => link(s) ^ br) ^
  link(new Delete).hide

  def specs = specifications(pattern = ".*Db.*")
} 

// start the database with this specification
class Create extends Specification { def is = xonly ^ 
  step("create database".pp)
}

// stop the database with this specification
class Delete extends Specification {  def is = xonly ^
  step("delete database".pp)
}

// an example of a specification using the database
// it will be invoked by the "Database" spec because 
// its name matches ".*Db.*"
class Db1Spec extends Specification { def is = s2"""
   test $db
   """
  def db = { println("use the database - 1"); ok }
}
class Db2Spec extends Specification { def is = s2"""
   test $db
   """
  def db = { println("use the database - 2"); ok }
}

当你运行:

sbt> test-only *Database* -- all

您应该会看到类似

的跟踪
create database
use the database - 1
use the database - 2
delete database