文件

§使用資料庫進行測試

雖然可以使用 ScalaTestspecs2 撰寫功能測試,透過啟動包含資料庫的完整應用程式來測試資料庫存取程式碼,但啟動完整應用程式通常並非理想,因為必須啟動並執行更多元件才能測試應用程式的其中一小部分,這會增加複雜度。

Play 提供多項公用程式,協助測試資料庫存取程式碼,讓程式碼可以在資料庫中測試,但與應用程式的其他部分隔離。這些公用程式可以輕鬆與 ScalaTest 或 specs2 搭配使用,而且可以讓資料庫測試更接近輕量且快速執行的單元測試,而非重量級且速度較慢的功能測試。

§使用資料庫

要使用資料庫後端進行測試,您只需要

libraryDependencies += jdbc % Test

要連線到資料庫,至少需要資料庫驅動程式名稱和資料庫的 URL,使用 Databases 伴隨物件。例如,要連線到 MySQL,可以使用下列內容

import play.api.db.Databases

val database = Databases(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://127.0.0.1/test"
)

這會為執行於 localhost 上的 MySQL test 資料庫建立資料庫連線池,名稱為 default。資料庫名稱僅供 Play 內部使用,例如,其他功能(例如演化)會使用此名稱來載入與該資料庫相關的資源。

您可能想要指定資料庫的其他設定,包括自訂名稱或設定屬性(例如使用者名稱、密碼和 Play 支援的各種連線池設定項目),方法是提供自訂名稱參數和/或自訂設定參數

import play.api.db.Databases

val database = Databases(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://127.0.0.1/test",
  name = "mydatabase",
  config = Map(
    "username" -> "test",
    "password" -> "secret"
  )
)

使用資料庫之後,由於資料庫通常由保留開啟連線的連線池支援,而且也可能執行執行緒,因此您需要關閉它。這可透過呼叫 shutdown 方法來完成

database.shutdown()

如果您使用在每個測試或套件周圍執行啟動/關閉程式碼的測試架構,則手動建立資料庫並關閉它會很有用。否則,建議您讓 Play 為您管理連線池。

§讓 Play 為您管理資料庫

Play 也提供一個 withDatabase 輔助函式,讓您可以提供一個程式碼區塊,以使用 Play 管理的資料庫連線池執行。Play 會確保在程式碼區塊執行完畢後正確關閉它

import play.api.db.Databases

Databases.withDatabase(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://127.0.0.1/test"
) { database =>
  val connection = database.getConnection()
  // ...
}

如同 Database.apply 工廠方法,withDatabase 也允許您傳遞自訂的 nameconfig 對應,如果您願意。

通常,直接從每個測試使用 withDatabase 會產生過多的樣板程式碼。建議您建立自己的輔助函式,以移除測試使用的這個樣板。例如

import play.api.db.Database
import play.api.db.Databases

def withMyDatabase[T](block: Database => T) = {
  Databases.withDatabase(
    driver = "com.mysql.jdbc.Driver",
    url = "jdbc:mysql://127.0.0.1/test",
    name = "mydatabase",
    config = Map(
      "username" -> "test",
      "password" -> "secret"
    )
  )(block)
}

然後,可以在每個測試中輕鬆使用它,並將樣板減至最低

withMyDatabase { database =>
  val connection = database.getConnection()
  // ...
}

提示:您可以使用這個來外化測試資料庫組態,使用環境變數或系統屬性來組態要使用的資料庫,以及如何連線到它。這讓開發人員可以彈性地設定自己的環境,以及提供可能與開發不同的特定環境的 CI 系統。

§使用記憶體中資料庫

有些人偏好不要安裝資料庫等基礎架構,以便執行測試。Play 提供簡單的輔助函式,以建立 H2 記憶體中資料庫,以供這些用途使用

import play.api.db.Databases

val database = Databases.inMemory()

可透過提供自訂名稱、自訂 URL 引數和自訂連線池設定來設定記憶體中資料庫。以下顯示提供 MODE 引數來告知 H2 模擬 MySQL,以及設定連線池以記錄所有陳述式

import play.api.db.Databases

val database = Databases.inMemory(
  name = "mydatabase",
  urlOptions = Map(
    "MODE" -> "MYSQL"
  ),
  config = Map(
    "logStatements" -> true
  )
)

與一般資料庫工廠一樣,請務必始終關閉記憶體中資料庫連線池

database.shutdown()

如果您未使用測試架構的 before/after 功能,您可能希望 Play 為您管理記憶體中資料庫的生命週期,這可以使用 withInMemory 輕鬆完成

import play.api.db.Databases

Databases.withInMemory() { database =>
  val connection = database.getConnection()

  // ...
}

withDatabase 一樣,建議您建立自己的方法來包裝 withInMemory 呼叫,以減少樣板程式碼

import play.api.db.Database
import play.api.db.Databases

def withMyDatabase[T](block: Database => T) = {
  Databases.withInMemory(
    name = "mydatabase",
    urlOptions = Map(
      "MODE" -> "MYSQL"
    ),
    config = Map(
      "logStatements" -> true
    )
  )(block)
}

§套用演化

在執行測試時,您通常會希望資料庫管理您的資料庫架構。如果您已使用演化,在測試中重複使用您在開發和製作中使用的相同演化通常是有意義的。您可能還想建立專門用於測試的自訂演化。Play 提供一些方便的輔助程式,可套用和管理演化,而無需執行整個 Play 應用程式。

若要套用演化,您可以從 Evolutions 伴隨物件使用 applyEvolutions

import play.api.db.evolutions._

Evolutions.applyEvolutions(database)

這將從 evolutions/<databasename> 目錄中的類別路徑載入演化,並套用它們。

在測試執行完畢後,您可能希望將資料庫重設為其原始狀態。如果您已實作演化向下指令碼,以便它們將刪除所有資料庫表格,您可以透過呼叫 cleanupEvolutions 方法來執行此操作

Evolutions.cleanupEvolutions(database)

§自訂演化

在某些情況下,您可能希望在測試中執行一些自訂演化。自訂演化可以使用自訂 EvolutionsReader 來使用。其中最簡單的是 SimpleEvolutionsReader,這是一個演化讀取器,它採用預先設定的資料庫名稱對應至 Evolution 腳本序列的對應,並且可以使用 SimpleEvolutionsReader 伴隨物件上的便利方法來建構。例如

import play.api.db.evolutions._

Evolutions.applyEvolutions(
  database,
  SimpleEvolutionsReader.forDefault(
    Evolution(
      1,
      "create table test (id bigint not null, name varchar(255));",
      "drop table test;"
    )
  )
)

清除自訂演化的方式與清除一般演化的方式相同,使用 cleanupEvolutions 方法

Evolutions.cleanupEvolutions(database)

但請注意,您不需要在此傳遞自訂演化讀取器,這是因為演化的狀態儲存在資料庫中,包括將用於終止資料庫的向下腳本。

有時將自訂演化腳本放入程式碼中是不切實際的。如果是這種情況,您可以將它們放在測試資源目錄中,使用 ClassLoaderEvolutionsReader 在自訂路徑下。例如

import play.api.db.evolutions._

Evolutions.applyEvolutions(database, ClassLoaderEvolutionsReader.forPrefix("testdatabase/"))

這將從 testdatabase/evolutions/<databasename>/<n>.sql 中載入演化,其結構與格式與開發和生產所做的相同。

如果您將演化腳本儲存在專案資料夾外,您可以使用 EnvironmentEvolutionsReader 從檔案系統上的絕對路徑或從專案資料夾中看到的相對路徑載入腳本

import play.api.Environment
import play.api.db.evolutions._

// Absolute path
Evolutions.applyEvolutions(
  database,
  new EnvironmentEvolutionsReader(Environment.simple(), "/opt/db_migration")
)

// Relative path (based on your project's root folder)
Evolutions.applyEvolutions(
  database,
  new EnvironmentEvolutionsReader(Environment.simple(), "../db_migration")
)

§允許 Play 管理演化

如果您使用測試架構在測試前後管理執行演化,則 applyEvolutionscleanupEvolutions 方法很有用。如果需要這種較輕量級的方法,Play 還提供了一個便利的 withEvolutions 方法來為您管理它

import play.api.db.evolutions._

Evolutions.withEvolutions(database) {
  val connection = database.getConnection()

  // ...
}

自然地,withEvolutions 可以與 withDatabasewithInMemory 結合使用以減少樣板程式碼,讓您可以定義一個同時建立資料庫和為您執行演化的函式

import play.api.db.Database
import play.api.db.Databases
import play.api.db.evolutions._

def withMyDatabase[T](block: Database => T) = {
  Databases.withInMemory(
    urlOptions = Map(
      "MODE" -> "MYSQL"
    ),
    config = Map(
      "logStatements" -> true
    )
  ) { database =>
    Evolutions.withEvolutions(
      database,
      SimpleEvolutionsReader.forDefault(
        Evolution(
          1,
          "create table test (id bigint not null, name varchar(255));",
          "drop table test;"
        )
      )
    ) {
      block(database)
    }
  }
}

定義好自訂資料庫管理方法後,我們就可以直接使用它們進行測試。

withMyDatabase { database =>
  val connection = database.getConnection()
  connection.prepareStatement("insert into test values (10, 'testing')").execute()

  connection
    .prepareStatement("select * from test where id = 10")
    .executeQuery()
    .next() must_== true
}

下一步:測試 Web 服務客戶端


發現本文件中的錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀完 文件指南 後,歡迎您貢獻一個 pull request。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。