grails create-controller com.example.simple
14 測試
版本 6.2.0
14 測試
自動化測試是 Grails 開發的重要面向。Grails 提供豐富的測試功能,從低階單元測試到高階功能測試。這份全面的指南將詳細探討這些多樣化的測試功能。
自動測試產生
當您使用 create-
和 generate-
命令時,Grails 會自動產生 unit
或 integration
測試。例如,如下所示執行 create-controller
命令
Grails 會在 grails-app/controllers/com/example/SimpleController.groovy 產生一個控制器,並在 src/test/groovy/com/example/SimpleControllerSpec.groovy 產生一個對應的單元測試。請務必注意,Grails 僅會建立測試結構;您需要實作測試邏輯。
執行測試
若要執行測試,您可以使用 Gradle check 任務
./gradlew check
此命令將執行 src/main/groovy/com/example/ 目錄中的所有單元測試。
鎖定測試
若要選擇性地針對測試執行,您有幾個選項
-
若要執行名為 SimpleController 的控制器的所有測試,請使用此指令
./gradlew check --tests SimpleController
-
若要測試所有以 Controller 結尾的類別,您可以使用萬用字元
./gradlew check --tests *Controller
-
若要指定套件名稱
./gradlew check --tests some.org.*Controller
-
若要執行套件中的所有測試
./gradlew check --tests some.org.*
-
若要執行套件中的所有測試,包括子套件
./gradlew check --tests some.org.**.*
-
若要針對特定測試方法
./gradlew check --tests SimpleController.testLogin
您可以根據需要組合多個模式
./gradlew check --tests some.org.* SimpleController.testLogin BookController
您可能需要在類別名稱之前指定套件名稱,並在後面加上「Spec」。例如,若要執行 ProductController 的測試,請使用 ./gradlew test *.ProductControllerSpec。如果您不想輸入整個套件層級結構,您也可以使用星號萬用字元。 |
除錯
若要使用遠端除錯工具除錯測試,您可以在任何指令中於 ./gradlew 之後加上 --debug-jvm,如下所示
./gradlew check --debug-jvm
這將開啟預設的 Java 遠端除錯埠 5005,讓您可以從您的程式碼編輯器或整合開發環境附加遠端除錯工具。
針對測試階段/分開執行單元和整合
若要執行「單元」測試,請使用此指令
./gradlew test
對於「整合」測試,您會執行
./gradlew integrationTest
在使用階段時針對測試
您可以組合測試和階段目標
./gradlew test some.org.**.*
此指令將執行 some.org 套件或其子套件中的單元階段中的所有測試。有關更詳細的資訊,建議您參閱 Gradle 關於 Java 和 JVM 專案測試 的文件。
14.1 單元測試
單元測試是「單元」層級的測試。換句話說,您是在測試個別方法或程式碼區塊,而不考慮周圍的基礎架構。單元測試通常在沒有涉及 I/O 的實體資源(例如資料庫、socket 連線或檔案)的情況下執行。這是為了確保它們執行得盡可能快,因為快速的回饋很重要。
自 Grails 3.3 以來,所有單元測試都使用 Grails 測試支援架構。此支援提供了一組特質。以下是可以看到的 hello world 測試範例
import spock.lang.Specification
import grails.testing.web.controllers.ControllerUnitTest
class HelloControllerTests extends Specification implements ControllerUnitTest<HelloController> {
void "Test message action"() {
when:"The message action is invoked"
controller.message()
then:"Hello is returned"
response.text == 'Hello'
}
}
有關使用 Grails 測試支援撰寫測試的更多資訊,請參閱 專屬文件。
低於 3.2 的 Grails 版本使用 Grails 測試混合架構,該架構基於 @TestMixin
AST 轉換。此函式庫已被更簡單且更友善的 IDE 特質基礎實作所取代。
14.2 整合測試
整合測試與單元測試不同,因為您可以在測試中完全存取 Grails 環境。您可以使用 create-integration-test 指令建立整合測試
$ grails create-integration-test Example
上述指令將在 src/integration-test/groovy/<PACKAGE>/ExampleSpec.groovy
位置建立新的整合測試。
Grails 使用測試環境進行整合測試,並在第一次測試執行前載入應用程式。所有測試都使用相同的應用程式狀態。
交易
整合測試方法預設會在自己的資料庫交易內執行,並在每個測試方法結束時回滾。這表示在測試期間儲存的資料不會持續存在於資料庫中(所有測試共用)。預設產生的整合測試範本包含 Rollback 標註
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class ExampleSpec extends Specification {
...
void "test something"() {
expect:"fix me"
true == false
}
}
Rollback
標註可確保每個測試方法都在會回滾的交易中執行。通常這是必要的,因為您不希望測試依賴於順序或應用程式狀態。
在 Grails 3.0 中,測試依賴於 grails.gorm.transactions.Rollback
標註,以便在整合測試中繫結工作階段。儘管每個測試方法交易都會回滾,但 setup()
方法會使用不會回滾的獨立交易。資料會持續存在於資料庫中,如果 setup()
設定資料並持續存在,則需要手動清除,如下面的範例所示
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setup() {
// Below line would persist and not roll back
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
expect:
Book.count() == 1
}
}
若要預先載入資料庫並自動回滾設定邏輯,任何持續性作業都必須從測試方法本身呼叫,以便它們可以在測試方法的回滾交易中執行。類似於使用下方所示的 setupData()
方法,它會在資料庫中建立記錄,並在執行其他測試後回滾
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setupData() {
// Below line would roll back
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
given:
setupData()
expect:
Book.count() == 1
}
}
使用 Spring 的 Rollback 標註
另一種交易方法可以改用 Spring 的 @Rollback。
import grails.testing.mixin.integration.Integration
import org.springframework.test.annotation.Rollback
import spock.lang.*
@Integration
@Rollback
class BookSpec extends Specification {
void setup() {
new Book(name: 'Grails in Action').save(flush: true)
}
void "test something"() {
expect:
Book.count() == 1
}
}
無法讓 grails.gorm.transactions.Rollback 的行為與 Spring 的 Rollback 標註相同,因為 grails.gorm.transactions.Rollback 會轉換類別的位元組碼,無需使用代理程式(Spring 的版本需要)。這點的缺點是您無法針對不同情況實作不同的方式(就像 Spring 在測試時所做的那樣)。
|
DirtiesContext
如果您有一系列會共用狀態的測試,您可以移除 Rollback
,而套件中的最後一個測試應具有 DirtiesContext 標註,它會關閉環境並重新啟動(請注意,這會影響測試執行時間)。
自動注入
若要取得 bean 的參考,您可以使用 Autowired 標註。例如
...
import org.springframework.beans.factory.annotation.*
@Integration
@Rollback
class ExampleServiceSpec extends Specification {
@Autowired
ExampleService exampleService
...
void "Test example service"() {
expect:
exampleService.countExamples() == 0
}
}
測試控制器
建議您使用 create-functional-test 指令建立 Geb 功能測試,以整合測試控制器。有關更多資訊,請參閱功能測試的下列部分。
14.3 功能測試
功能測試包括對執行中的應用程式進行 HTTP 要求,並驗證結果行為。這對於端對端測試場景很有用,例如對 JSON API 進行 REST 呼叫。
Grails 預設會附帶使用 Geb 架構 撰寫功能測試的支援。若要建立功能測試,您可以使用 create-functional-test
指令,它會建立新的功能測試
$ grails create-functional-test MyFunctional
上述指令會在 src/integration-test/groovy
目錄中建立新的 Spock 規格,稱為 MyFunctionalSpec.groovy
。測試會加上 Integration 註解,以指出它是整合測試,並延伸 GebSpec
超類別
@Integration
class HomeSpec extends GebSpec {
def setup() {
}
def cleanup() {
}
void "Test the home page renders correctly"() {
when:"The home page is visited"
go '/'
then:"The title is correct"
$('title').text() == "Welcome to Grails"
}
}
執行測試時,應用程式容器會在背景中載入,您可以使用 Geb API 將要求傳送至執行中的應用程式。
請注意,應用程式只會在整個測試執行期間載入一次,因此功能測試會在整個套件中共享應用程式的狀態。
此外,應用程式會在 JVM 中載入為測試,這表示測試可以完全存取應用程式狀態,並可以直接與資料服務 (例如 GORM) 互動,以設定和清除測試資料。
Integration
註解支援選擇性的 applicationClass
屬性,可用於指定要使用於功能測試的應用程式類別。該類別必須延伸 GrailsAutoConfiguration。
@Integration(applicationClass=com.demo.Application)
class HomeSpec extends GebSpec {
// ...
}
如果未指定 applicationClass
,則測試執行時間環境會嘗試動態找出應用程式類別,這在可能存在多個應用程式類別的多專案建置中可能會造成問題。
預設會隨機指定執行伺服器埠。Integration
註解會將 serverPort
屬性新增至測試類別,如果您想知道應用程式在什麼埠上執行,可以使用這個屬性,如果您如上所示延伸 GebSpec
,則不需要這個屬性,但這可能是很有用的資訊。
如果您想在固定埠上執行測試 (由 server.port
組態屬性定義),您需要手動使用 @SpringBootTest
註解您的測試
import grails.testing.mixin.integration.Integration
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
@Integration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class MySpec extends Specification {
// ...
}