(快速參考)

14 測試

版本 6.2.0

14 測試

自動化測試是 Grails 開發的重要面向。Grails 提供豐富的測試功能,從低階單元測試到高階功能測試。這份全面的指南將詳細探討這些多樣化的測試功能。

自動測試產生

當您使用 create-generate- 命令時,Grails 會自動產生 unitintegration 測試。例如,如下所示執行 create-controller 命令

grails create-controller com.example.simple

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/ 目錄中的所有單元測試。

鎖定測試

若要選擇性地針對測試執行,您有幾個選項

  1. 若要執行名為 SimpleController 的控制器的所有測試,請使用此指令

    ./gradlew check --tests SimpleController
  2. 若要測試所有以 Controller 結尾的類別,您可以使用萬用字元

    ./gradlew check --tests *Controller
  3. 若要指定套件名稱

    ./gradlew check --tests some.org.*Controller
  4. 若要執行套件中的所有測試

    ./gradlew check --tests some.org.*
  5. 若要執行套件中的所有測試,包括子套件

    ./gradlew check --tests some.org.**.*
  6. 若要針對特定測試方法

    ./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 {

    // ...

}