Пройти тесты версии 2.4 с помощью Slick, spec2 и Postgresql.

Я хочу запускать свои тесты с тем же механизмом базы данных, теми же эволюциями и конфигурацией, что и в производстве. Моя БД — PostgreSQL 9.4, и я использую Slick 3.0.0 для доступа к ней.

А тут проблемы:

  • В случае выполнения параллельных тестов у меня одновременно выполняется несколько эволюций на одной и той же базе данных. Это приводит к ошибкам.
  • В случае последовательного выполнения тестов у меня другая ошибка с пулом потоков.

Здесь подробности.

Я использую эволюцию для инициализации базы данных для каждого теста. Для этого я подготовил базовый класс спецификации:

 class DatabaseSpecification extends PlaySpecification {
  protected val defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

  protected def afterEach(app: Application) = {
    recreateDbSchema(app)
  }

  private def recreateDbSchema(app: Application) = {
    val dbConfig = DatabaseConfigProvider.get[JdbcProfile](app)
    import dbConfig.driver.api._

    val recreateSchema: DBIO[Unit] = DBIO.seq(
      sqlu"drop schema public cascade",
      sqlu"create schema public"
    )    
    Await.ready(dbConfig.db.run(recreateSchema), 5 seconds)
  }

  abstract class DatabaseContext() extends WithApplication(defaultAppBuilder.build()) {
    protected val injector = implicitApp.injector

    override def around[T](t: => T)(implicit evidence$2: AsResult[T]): Result = super.around {
      try {
        t
      } finally {
        afterEach(implicitApp)
      }
    }
  }

}

где application.test.override.conf — конфигурационный файл для тестов.

Также есть пара тестов в спецификации предка:

"save1 and query" in new DatabaseContext {
  // create new user
  val accountRepo = injector.instanceOf[SystemUserRepo]
  val user = new SystemUser(id = 0, login = "admin", passwordHash = "", role = Role.Administrator)
  val futureUserId = accountRepo.create(user)

  // check if user id is greater then zero
  val userId = await(futureUserId)(5 second)
  userId must be_>(0)
}

"second one1" in new DatabaseContext {
  1 mustEqual 1
}

Если я запускаю все тесты спецификации параллельно (по умолчанию), то получаю исключение Database 'default' is in an inconsistent state![An evolution has not been applied properly. Please check the problem and resolve it manually before marking it as resolved.] @6mk338l87: Database 'default' is in an inconsistent state! в одном из тестов.

Если я запускаю его последовательно, class AccountRepositoryTest extends DatabaseSpecification { sequential ... } появляется другое исключение

Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@2744dcae rejected from java.util.concurrent.ThreadPoolExecutor@16d0e521[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 3]
java.util.concurrent.RejectedExecutionException: Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@2744dcae rejected from java.util.concurrent.ThreadPoolExecutor@16d0e521[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 3]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at scala.concurrent.impl.ExecutionContextImpl$$anon$1.execute(ExecutionContextImpl.scala:136)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runSynchronousDatabaseAction(DatabaseComponent.scala:224)
    at slick.jdbc.JdbcBackend$DatabaseDef.runSynchronousDatabaseAction(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runInContext(DatabaseComponent.scala:201)
    at slick.jdbc.JdbcBackend$DatabaseDef.runInContext(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runInternal(DatabaseComponent.scala:75)
    at slick.jdbc.JdbcBackend$DatabaseDef.runInternal(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.run(DatabaseComponent.scala:72)
    at slick.jdbc.JdbcBackend$DatabaseDef.run(JdbcBackend.scala:38)
    at repository.GenericCRUD$class.create(GenericCRUD.scala:50)
    at repository.GenericCRUDImpl.create(GenericCRUD.scala:70)
    at unit.repositories.AccountRepositoryTest$$anonfun$3$$anon$1.delayedEndpoint$unit$repositories$AccountRepositoryTest$$anonfun$3$$anon$1$1(AccountRepositoryTest.scala:39)
    at unit.repositories.AccountRepositoryTest$$anonfun$3$$anon$1$delayedInit$body.apply(AccountRepositoryTest.scala:35)

Это происходит на линии val futureUserId = accountRepo.create(user). К сожалению, я понятия не имею, почему выбрасывается второе исключение. Кстати, я, конечно, предпочитаю запускать тесты параллельно, но не знаю, как этого добиться.

Любая помощь будет оценена по достоинству!


person sedovav    schedule 02.07.2015    source источник
comment
Чтобы имитировать любую среду JDBC во время тестов, вы можете воспользоваться подходом Acolyte, который позволяет проводить изолированные модульные тесты. для персистентного слоя.   -  person cchantep    schedule 03.07.2015
comment
Боюсь, мне это не поможет, потому что у меня есть sql-функции в моей БД.   -  person sedovav    schedule 04.07.2015
comment
Поскольку модульное тестирование уровня постоянства направлено не на проверку механизма и/или функций БД, а на тестирование кода приложения, которое работает с этой БД, требование состоит в том, чтобы иметь возможность определять в этих тестах некоторые соединения JDBC, принимающие одни и те же оператор и возвращает те же результаты JDBC (набор результатов | количество обновлений | ошибка), что и при воспроизведении с DB. Acolyte делает это, независимо от оператора.   -  person cchantep    schedule 04.07.2015


Ответы (1)


В настоящее время вы должны запускать тесты базы данных с sequential, иначе это не удастся. Ошибка пула потоков происходит из:

protected val defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

это должна быть функция:

protected def defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

тогда он начнет работать. Мы также делаем это, однако у нас есть абстрактный класс, в котором есть val application, где мы можем настраивать вещи, и def app, который просто выполняет def app = application.build(), чтобы мы все еще могли расширять GuiceApplicationBuilder.

В настоящее время у меня сначала была такая же ошибка, вы определенно не хотите, чтобы одно и то же приложение использовалось повторно для каждого теста.

Так что попробуйте, и у вас будет действительно хороший набор функциональных тестов для игровых приложений.

Однако единственное, чего мы не делаем, — это public drop schema мы полагаемся на эволюции, чтобы исправить это, очистив их. Если они ошибаются, мы сразу видим ошибки.

person Christian Schmitt    schedule 04.07.2015
comment
Привет Кристиан! Я изменил val на def, но, к сожалению, у меня та же ошибка с пулом потоков, что и раньше. Подскажите пожалуйста, как вы запускаете эволюционные дауны после теста? - person sedovav; 04.07.2015
comment
Мои Evolution работают следующим образом: override def after = { val dbapi = app.injector.instanceOf[DBApi] Evolutions.cleanupEvolutions(dbapi.database(prefix)) } , а затем with AfterEach на тестовых занятиях. Однако это работает и с вашим DatabaseContext. - person Christian Schmitt; 06.07.2015
comment
Кристиан, если не возражаешь, покажи мне свой базовый класс, пожалуйста. - person sedovav; 08.07.2015