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 检查任务
./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 的物理资源(例如数据库、套接字连接或文件)的情况下运行。这是为了确保它们尽可能快地运行,因为快速反馈很重要。
从 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
目录中创建一个名为 MyFunctionalSpec.groovy
的新 Spock 规范。该测试使用 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 {
// ...
}