Тестирование приложения Play + Slick

У меня есть простое приложение CRUD, созданное с помощью Scala Play 2.4.3 и Play-slick 1.1.0 (slick 3.1.0), которое использует базу данных MySQL для постоянного хранения.

Я пытался создать тесты для своего приложения и увидел 2 основных варианта:

  • фиктивный доступ к базе данных, который, насколько я видел, требует некоторых изменений кода
  • заставить тесты использовать альтернативную базу данных (возможно, в памяти H2).

Каков наилучший подход (преимущества и недостатки)?

Я предпочитаю второй подход, но у меня возникают некоторые трудности при настройке тестов.

Что мне нужно сделать? Во-первых, я думаю, что мне нужно выполнить тесты с FakeApplication. , Правильно? Нужна ли мне какая-либо зависимость sbt, чтобы иметь возможность сделать это?

После этого, как мне указать использовать базу данных H2?


person pedrorijo91    schedule 29.11.2015    source источник


Ответы (2)


У меня была такая же борьба, и я придумал такое решение (используя второй подход):

Создайте контекст для использования DAO:

trait BaseContext{

  def dbName: String

  val dbConfig = DatabaseConfigProvider.get[JdbcProfile](dbName)
  val db = dbConfig.db
  val profile = dbConfig.driver
  val tables = new Tables {  // this is generated by Schema Code Generator
    override val profile: JdbcProfile = dbConfig.driver
  }
}

@Singleton
class AppContext extends BaseContext{
  def dbName = "mysql"  // name in your conf right after "slick.dbs"
}

@Singleton
class TestingContext extends BaseContext{
  def dbName = "h2"
}

Затем создайте модуль для привязки инъекции, и не забудьте включить его в конфиге с помощью play.modules.enabled += "your.Module":

class ContextModule(environment: Environment, configuration: Configuration) extends AbstractModule {

  override def configure(): Unit = {
    if (configuration.getString("app.mode").contains("test")) {
      bind(classOf[BaseContext])
          .to(classOf[TestingContext])
    } else {
      bind(classOf[BaseContext])
          .to(classOf[AppContext])
    }
  }
}

И внедрите его в каждый созданный вами DAO:

class SomeDAO @Inject()(context: BaseContext){

  val dbConfig = context.dbConfig
  val db = context.db
  val tables = context.tables
  import tables.profile.api._

  def otherStuff....
  // you can call db.run(...), tables.WhateverYourTableIs, tables.TableRowCaseClass, ...
}

И последний шаг, ваш файл конфигурации. В моем случае я использовал app.mode для обозначения среды, и я использую отдельные .conf для другой среды. Конечно, в этих конфах у вас должна быть правильная конфигурация БД. Вот образец:

app.mode = "test"

# Database configuration
slick.dbs = {
  # for unit test
  h2 {
    driver = "slick.driver.H2Driver$"
    db = {
      url = "jdbc:h2:mem:test;MODE=MYSQL"
      driver = "org.h2.Driver"
      keepAliveConnection = true
    }
  }
}

Я почти уверен, что мое решение не элегантное, но оно приносит пользу. :) Любое лучшее решение приветствуется!

person noru    schedule 03.12.2015
comment
Оказывается, у Typesafe есть проект, демонстрирующий именно это. github.com/typesafehub/activator-slick-multidb - person Abhijit Sarkar; 30.08.2016
comment
Я хорошо знаю этот пример, и он дал мне некоторые подсказки. Однако это не оправдало моих ожиданий на 100%. - person noru; 30.08.2016
comment
Чего тебе не хватало? В последней ветке, 3.1, есть то, что вы показали выше, и еще кое-что. - person Abhijit Sarkar; 30.08.2016
comment
Возьмем, к примеру, этот демонстрационный проект, он демонстрирует, как легко получить доступ к 2 БД во время выполнения, в то время как моя главная цель — заменить mysql на h2 в тестах с ТАКОЙ же бизнес-логикой (один набор кода). Это означает, что мне не нужно заботиться о БД при кодировании реальных функций или модульных тестов. Это делает файл conf лучшим местом, поэтому я кодирую свой путь к нему. После этого я полностью доволен и не стал отслеживать новейший слик. Если есть лучший способ сделать это, пожалуйста, просветите меня. Это действительно беспокоило меня долгое время. - person noru; 30.08.2016
comment
Я сам в этом разбираюсь. Кажется, они не предлагают хороший способ сделать это. Я запускаю H2 в памяти в тестах и ​​имею 2 DAO, один с использованием Slick ORM, другой с простым SQL. Существуют различные настройки, которые, кажется, случайным образом влияют на тесты, например, включен ли пул соединений, PatienceConfig (из org.scalatest.concurrent). Я создаю и удаляю тестового пользователя до и после каждого теста, а иногда пользователь удаляется до завершения теста. Если я запускаю транзакцию, результаты будут другими. Ничего из этого не происходит с MongoDB, только RDBMS (в памяти?) - person Abhijit Sarkar; 30.08.2016
comment
Не стесняйтесь проверить мой проект, если вы заинтересованы. github.com/abhijitsarkar/akka/tree/master/user-service - person Abhijit Sarkar; 30.08.2016
comment
Да, это может быть чертовски больно. После долгого периода отладки и вопросов жизни я решил приспособиться к этому параллельному миру. Я начал понимать, что внутри этих тестовых потоков живет несколько экземпляров h2, и я не мог определить их порядок. Это редкость, потому что реальная БД считается единственной правдой. - person noru; 30.08.2016

мое решение состояло в том, чтобы добавить step(Play.start(fakeApp)) в начало каждой спецификации и step(Play.stop(fakeApp)) в конец каждой спецификации.

Также:

def fakeApp: FakeApplication = {
FakeApplication(additionalConfiguration =
  Map(
    "slick.dbs.default.driver" -> "slick.driver.H2Driver$",
    "slick.dbs.default.db.driver" -> "org.h2.Driver",
    "slick.dbs.default.db.url" -> "jdbc:h2:mem:play"
  ))

}

Это было необходимо, потому что я использую play-slick, что требует таких конфигураций, как:

slick.dbs.default.driver = "slick.driver.MySQLDriver$"
slick.dbs.default.db.driver = "com.mysql.jdbc.Driver"
slick.dbs.default.db.url = "jdbc:mysql://localhost/database"
slick.dbs.default.db.user = "user"
slick.dbs.default.db.password = "password"

дополнительную информацию можно найти в документах.

person pedrorijo91    schedule 06.12.2015
comment
Что это step? Ключевое слово не легко найти. - person JulienD; 03.02.2017
comment
Скорее всего функция от spec2, которая позволяет запускать определенные вещи до и/или после тестов. Подобно тому, для чего вы использовали бы beforeAll / afterAll. - person rethab; 04.03.2018