Specs2在整个套装之前和之后设置环境

时间:2015-03-30 02:58:04

标签: scala amazon-dynamodb spray specs2

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

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
  }
}

然后可以使用DynamoDBSpec扩展任何测试类,并创建表。一切正常,直到从多个测试类扩展DynamoDBSpec,在此期间抛出ResourceInUseException:'无法创建预先存在的表'。原因是它们并行执行,因此它希望同时执行表创建。

我试图通过在顺序模式下运行测试来克服它,但在all和afterall之前仍然并行执行。

理想情况下,我认为在整个套件运行之前创建表而不是每个Spec类调用都是好的,然后在整个套件完成后将它们拆除。有谁知道如何实现这个目标?

1 个答案:

答案 0 :(得分:4)

有两种方法可以实现这一目标。

使用对象

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

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的规范之间创建依赖关系。这意味着您可以定义一个“套件”规范:

  1. 启动数据库
  2. 收集所有相关规范
  3. 删除数据库
  4. 例如

    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