scala - Play2 和 Scala,我应该如何配置集成测试以使用正确的数据库运行

标签 scala testing h2 playframework-2.1 anorm

我正在尝试弄清楚如何在 Play2 应用程序中编写数据库集成测试。

在我的conf文件中,我指定了两个数据库,xxx_test用于常规使用,h2 db用于测试:

db.xxx_test.driver=com.mysql.jdbc.Driver
db.xxx_test.url="jdbc:mysql://localhost/xxx_test?characterEncoding=UTF-8"
db.xxx_test.user="root"
db.xxx_test.password=""

db.h2.driver=org.h2.Driver
db.h2.url="jdbc:h2:mem:play"
db.h2.user=sa
db.h2.password=""

在我的用户对象中,我指定了运行应用程序时要使用的xxx_test

def createUser(user: User): Option[User] = {

    DB.withConnection("xxx_test") {
      implicit connection =>
        SQL("insert into users(first_name, last_name, email, email_validated, last_login, created, modified, active) values({first_name},{last_name},{email},{email_validated},{last_login}, {created}, {modified}, {active})").on(
          'first_name -> user.firstName,
          'last_name -> user.lastName,
          'email -> user.email,
          'email_validated -> user.emailValidated,
          'last_login -> user.lastLogin,
          'created -> user.created,
          'modified -> user.modified,
          'active -> true
        ).executeInsert().map(id => {
          return Some(User(new Id[Long](id), user.firstName, user.lastName, user.email, user.emailValidated, user.lastLogin, user.created, user.modified, true))
        }
        )
    }
    None
  }

在我的测试中,我创建一个新的 inMemoryDatabase,然后使用 User 创建并获取我的对象 用于测试。

class DBEvolutionsTest extends Specification {

  "The Database" should {
    "persist data properly" in {
      running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

        User.create(User(Id[Long](1L), "jakob",
          "aa",
          "aaa",
          true,
          DateTime.now(),
          DateTime.now(),
          DateTime.now(),
          true)) 

        val newUser = User.findBy(Id[Long](1L))

        newUser.get.firstName must beEqualTo("jakob")
      }
      }
    }

}

这当然不是正确的方法,因为 User 对象使用 xxx_test 而不是 h2 数据库。该测试将在真实数据库中创建一个用户,而不是内存中的用户,因为我已在 User(DB.withConnection("xxx_test")) 对象中指定了数据库。 我想有一些聪明的方法可以做到这一点,我不想传递数据库名称 像 User.create(User(...), "xxx_test")

这样的应用程序

你是如何解决这个问题的?

最佳答案

您可能想了解如何在 scala 中进行依赖项注入(inject)。一个好的解决方案是将数据库从用户模型中抽象出来,然后将其作为依赖项传递。

一个简单的方法是更改​​配置文件以进行测试。玩让你specify which config file is used on the command line 。 但这不是最实用的。

另一个解决方案是使用隐式,将数据库连接定义为函数的隐式参数:

def createUser(user: User)(implicit dbName: String): Option[User]= 
  DB.withConnection(dbName) { ... }

您仍然需要在所有调用中向上传播参数,但您可以隐藏它: def importUsers(csvFile: File)(implicit dbName: String): Seq[User] = { conn => ... 用户.createUser(u) ... }

当你从顶部调用它时:

implicit dbName = "test"
importUsers(...)

这是在 scala 中构建的,因此设置起来非常容易,并且不需要大量的样板文件支持。就我个人而言,我认为隐式会使代码不清楚,我更喜欢本演示文稿中提出的解决方案 Dead-Simple Dependency Injection .

其要点是,您使 createUser 以及依赖于数据库连接的所有其他方法根据连接返回函数,而不仅仅是结果。以下是它如何与您的示例配合使用。

1- 您创建一个配置连接的连接特征。一个简单的形式是:

trait ConnectionConfig {
   def dbName: String
}

2-您的方法取决于该配置返回一个函数:

def createUser(user: User): ConnectionConfig => Option[User] = { conn =>

    DB.withConnection(conn.dbName) { ... }

}

3-当您在另一个方法中使用 createUser 时,该方法也变得依赖于连接,因此您可以通过使用函数返回类型返回对 ConnectionConfig 的依赖关系来标记它,例如:

def importUsers(csvFile: File): ConnectionConfig => Seq[User] = { conn =>
    ...
    User.createUser(u)(conn)
    ...
}

这是一个好习惯,因为您的代码中会清楚哪些方法依赖于与数据库的连接,并且您可以轻松地交换连接。因此,在您的主应用程序中,您将创建真正的连接:

class RealConncetionConfig extends ConnectionConfig {
   val dbName = "xxx_test"
}

但在您的测试文件中,您创建一个测试数据库配置:

class DBEvolutionsTest extends Specification {

  class TestDBConfig extends ConnectionConfig {
      val dbName = "h2"
  }

  val testDB = new TestDBConfig()

  "The Database" should {
    "persist data properly" in {
      running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

        User.create(User(Id[Long](1L), "jakob",
          "aa",
          "aaa",
          true,
          DateTime.now(),
          DateTime.now(),
          DateTime.now(),
          true))(testDB) 

        val newUser = User.findBy(Id[Long](1L))

        newUser.get.firstName must beEqualTo("jakob")
      }
      }
    }

}

这就是它的要点。查看我提到的演示文稿和幻灯片,有一个很好的方法来抽象所有这些,以便您可以释放使此代码变得丑陋的 (conn) 参数。

顺便说一句,如果我是你,我什至会抽象出数据库的类型。因此,不要将 SQL 放在 User 模型对象中,而是将其放在单独的实现中,这样您就可以轻松切换数据库类型(使用 mongodb、dynamo...)。 它就像这样,从之前的代码扩展而来:

trait ConnectionConfig {
   def createUser(user: User): Option[User]
}

在用户模型对象中:

def createUser(user: User): ConnectionConfig => Option[User] = { conn =>
    conn.createUser(user)
}
这样,当根据用户模型测试部分代码时,您可以创建一个模拟数据库,其中 createUser 始终有效并返回预期结果(或始终失败...),甚至无需使用内存数据库(您仍然需要测试真正的 SQL 连接,但您可以测试应用程序的其他部分):

trait ConnectionConfig {
    def createUser(user: User): Option[User] = Some(user)
}

关于scala - Play2 和 Scala,我应该如何配置集成测试以使用正确的数据库运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15505234/

相关文章:

mysql - org.h2.jdbc.JdbcSQL异常 : Column "ID" not found

h2 - 使用Datagrip客户端连接到H2数据库

scala - sparkSession.sparkContext对于本地Spark集群为null

scala - 如何使用elastic4s编写索引的映射/设置?

android - Android 游戏的配置文件,以舒适的方式测试/调整游戏

python - 为什么 pytest 覆盖会跳过自定义异常消息断言?

mysql - 使用 DISTINCT 关键字将 GROUP_CONCAT 查询从 MySQL 转换为 H2 很困难

scala - 为什么以下 scala 实验代码不起作用?

scala - 从 Seq 中删除一个元素

ios - 当模式对话框显示时,GREYAction 不起作用