在 scalatest 中创建和删除 scala slick 表之前和之后的异步
Async before and after for creating and dropping scala slick tables in scalatest
我正在尝试找出一种方法来使用异步 before
和 after
语句,其中下一个测试用例 运行 直到完成内部的操作测试用例。在我的例子中,它是在数据库
中创建和删除 table
val table = TableQuery[BlockHeaderTable]
val dbConfig: DatabaseConfig[PostgresDriver] = DatabaseConfig.forConfig("databaseUrl")
val database: Database = dbConfig.db
before {
//Awaits need to be used to make sure this is fully executed before the next test case starts
//TODO: Figure out a way to make this asynchronous
Await.result(database.run(table.schema.create), 10.seconds)
}
"BlockHeaderDAO" must "store a blockheader in the database, then read it from the database" in {
//...
}
it must "delete a block header in the database" in {
//...
}
after {
//Awaits need to be used to make sure this is fully executed before the next test case starts
//TODO: Figure out a way to make this asynchronous
Await.result(database.run(table.schema.drop),10.seconds)
}
有没有一种简单的方法可以删除 before
和 after
函数中的这些 Await
调用?
以下是 Dennis Vriend 在他的 slick-3.2.0-test 项目中采用的测试方法。
首先,定义一个dropCreateSchema
方法。此方法尝试创建一个 table;如果该尝试失败(例如,因为 table 已经存在),它会删除,然后创建 table:
def dropCreateSchema: Future[Unit] = {
val schema = BlockHeaderTable.schema
db.run(schema.create)
.recoverWith {
case t: Throwable =>
db.run(DBIO.seq(schema.drop, schema.create))
}
}
其次,定义一个 createEntries
方法,用一些样本数据填充 table 以供每个测试用例使用:
def createEntries: Future[Unit] = {
val setup = DBIO.seq(
// insert some rows
BlockHeaderTable ++= Seq(
BlockHeaderTableRow(/* ... */),
// ...
)
).transactionally
db.run(setup)
}
三、定义一个initialize
方法依次调用上面两个方法:
def initialize: Future[Unit] = for {
_ <- dropCreateSchema
_ <- createEntries
} yield ()
在测试 class 中,混入 ScalaFutures
特征。例如:
class TestSpec extends FlatSpec
with Matchers
with ScalaFutures
with BeforeAndAfterAll
with BeforeAndAfterEach {
// ...
}
同样在测试 class 中,定义从 Future
到 Try
的隐式转换,并覆盖 beforeEach
方法以调用 initialize
:
implicit val timeout: Timeout = 10.seconds
implicit class PimpedFuture[T](self: Future[T]) {
def toTry: Try[T] = Try(self.futureValue)
}
override protected def beforeEach(): Unit = {
blockHeaderRepo.initialize // in this example, initialize is defined in a repo class
.toTry recover {
case t: Throwable =>
log.error("Could not initialize the database", t)
} should be a 'success
}
override protected def afterAll(): Unit = {
db.close()
}
有了上面的部分,就不需要Await
。
你可以简化@Jeffrey Chung
简化的dropCreateSchema
方法:
def dropCreateSchema: Future[Unit] = {
val schema = users.schema
db.run(DBIO.seq(schema.dropIfExists, schema.create))
}
同样在测试 class 中,我简化了调用 initialize
的 beforeEach
方法。我删除了从 Future
到 Try
的隐式转换,并使用 onComplete
回调:
override protected def beforeEach(): Unit = {
initialize.onComplete(f =>
f recover {
case t: Throwable =>
log.error("Could not initialize the database", t)
} should be a 'success)
}
override protected def afterAll(): Unit = {
db.close()
}
不幸的是,@Jeffrey Chung 的解决方案对我来说是悬而未决的(因为 futureValue
实际上在内部等待)。这就是我最终做的:
import org.scalatest.{AsyncFreeSpec, FutureOutcome}
import scala.concurrent.Future
class TestTest extends AsyncFreeSpec /* Could be any AsyncSpec. */ {
// Do whatever setup you need here.
def setup(): Future[_] = ???
// Cleanup whatever you need here.
def tearDown(): Future[_] = ???
override def withFixture(test: NoArgAsyncTest) = new FutureOutcome(for {
_ <- setup()
result <- super.withFixture(test).toFuture
_ <- tearDown()
} yield result)
}
我正在尝试找出一种方法来使用异步 before
和 after
语句,其中下一个测试用例 运行 直到完成内部的操作测试用例。在我的例子中,它是在数据库
val table = TableQuery[BlockHeaderTable]
val dbConfig: DatabaseConfig[PostgresDriver] = DatabaseConfig.forConfig("databaseUrl")
val database: Database = dbConfig.db
before {
//Awaits need to be used to make sure this is fully executed before the next test case starts
//TODO: Figure out a way to make this asynchronous
Await.result(database.run(table.schema.create), 10.seconds)
}
"BlockHeaderDAO" must "store a blockheader in the database, then read it from the database" in {
//...
}
it must "delete a block header in the database" in {
//...
}
after {
//Awaits need to be used to make sure this is fully executed before the next test case starts
//TODO: Figure out a way to make this asynchronous
Await.result(database.run(table.schema.drop),10.seconds)
}
有没有一种简单的方法可以删除 before
和 after
函数中的这些 Await
调用?
以下是 Dennis Vriend 在他的 slick-3.2.0-test 项目中采用的测试方法。
首先,定义一个dropCreateSchema
方法。此方法尝试创建一个 table;如果该尝试失败(例如,因为 table 已经存在),它会删除,然后创建 table:
def dropCreateSchema: Future[Unit] = {
val schema = BlockHeaderTable.schema
db.run(schema.create)
.recoverWith {
case t: Throwable =>
db.run(DBIO.seq(schema.drop, schema.create))
}
}
其次,定义一个 createEntries
方法,用一些样本数据填充 table 以供每个测试用例使用:
def createEntries: Future[Unit] = {
val setup = DBIO.seq(
// insert some rows
BlockHeaderTable ++= Seq(
BlockHeaderTableRow(/* ... */),
// ...
)
).transactionally
db.run(setup)
}
三、定义一个initialize
方法依次调用上面两个方法:
def initialize: Future[Unit] = for {
_ <- dropCreateSchema
_ <- createEntries
} yield ()
在测试 class 中,混入 ScalaFutures
特征。例如:
class TestSpec extends FlatSpec
with Matchers
with ScalaFutures
with BeforeAndAfterAll
with BeforeAndAfterEach {
// ...
}
同样在测试 class 中,定义从 Future
到 Try
的隐式转换,并覆盖 beforeEach
方法以调用 initialize
:
implicit val timeout: Timeout = 10.seconds
implicit class PimpedFuture[T](self: Future[T]) {
def toTry: Try[T] = Try(self.futureValue)
}
override protected def beforeEach(): Unit = {
blockHeaderRepo.initialize // in this example, initialize is defined in a repo class
.toTry recover {
case t: Throwable =>
log.error("Could not initialize the database", t)
} should be a 'success
}
override protected def afterAll(): Unit = {
db.close()
}
有了上面的部分,就不需要Await
。
你可以简化@Jeffrey Chung
简化的dropCreateSchema
方法:
def dropCreateSchema: Future[Unit] = {
val schema = users.schema
db.run(DBIO.seq(schema.dropIfExists, schema.create))
}
同样在测试 class 中,我简化了调用 initialize
的 beforeEach
方法。我删除了从 Future
到 Try
的隐式转换,并使用 onComplete
回调:
override protected def beforeEach(): Unit = {
initialize.onComplete(f =>
f recover {
case t: Throwable =>
log.error("Could not initialize the database", t)
} should be a 'success)
}
override protected def afterAll(): Unit = {
db.close()
}
不幸的是,@Jeffrey Chung 的解决方案对我来说是悬而未决的(因为 futureValue
实际上在内部等待)。这就是我最终做的:
import org.scalatest.{AsyncFreeSpec, FutureOutcome}
import scala.concurrent.Future
class TestTest extends AsyncFreeSpec /* Could be any AsyncSpec. */ {
// Do whatever setup you need here.
def setup(): Future[_] = ???
// Cleanup whatever you need here.
def tearDown(): Future[_] = ???
override def withFixture(test: NoArgAsyncTest) = new FutureOutcome(for {
_ <- setup()
result <- super.withFixture(test).toFuture
_ <- tearDown()
} yield result)
}