单元测试教程


示例项目

asgard-service

技术

框架:spock

语言:groovy

pom依赖和插件

  1. <!-- Test Dependencies -->
  2. <dependencies>
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-test</artifactId>
  6. <scope>test</scope>
  7. </dependency>
  8. <dependency>
  9. <groupId>io.choerodon</groupId>
  10. <artifactId>choerodon-liquibase</artifactId>
  11. <version>${choerodon.starters.version}</version>
  12. <scope>test</scope>
  13. </dependency>
  14. <dependency>
  15. <groupId>com.h2database</groupId>
  16. <artifactId>h2</artifactId>
  17. <version>1.4.197</version>
  18. <scope>test</scope>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.spockframework</groupId>
  22. <artifactId>spock-core</artifactId>
  23. <version>1.1-groovy-2.4-rc-2</version>
  24. <scope>test</scope>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.spockframework</groupId>
  28. <artifactId>spock-spring</artifactId>
  29. <version>1.1-groovy-2.4-rc-3</version>
  30. <scope>test</scope>
  31. </dependency>
  32. <!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
  33. <dependency>
  34. <groupId>cglib</groupId>
  35. <artifactId>cglib-nodep</artifactId>
  36. <version>2.2</version>
  37. <scope>test</scope>
  38. </dependency>
  39. </dependencies>
  40. <build>
  41. <finalName>app</finalName>
  42. <plugins>
  43. <plugin>
  44. <groupId>org.codehaus.gmavenplus</groupId>
  45. <artifactId>gmavenplus-plugin</artifactId>
  46. <version>1.5</version>
  47. <executions>
  48. <execution>
  49. <goals>
  50. <goal>addTestSources</goal>
  51. <goal>testCompile</goal>
  52. </goals>
  53. </execution>
  54. </executions>
  55. </plugin>
  56. <plugin>
  57. <groupId>org.apache.maven.plugins</groupId>
  58. <artifactId>maven-compiler-plugin</artifactId>
  59. <version>3.3</version>
  60. <configuration>
  61. <source>1.8</source>
  62. <target>1.8</target>
  63. </configuration>
  64. </plugin>
  65. <plugin>
  66. <groupId>org.apache.maven.plugins</groupId>
  67. <artifactId>maven-surefire-plugin</artifactId>
  68. <version>2.18.1</version>
  69. <configuration>
  70. <includes>
  71. <include>**/*Test.java</include>
  72. <include>**/*Spec.java</include>
  73. </includes>
  74. </configuration>
  75. </plugin>
  76. </plugins>
  77. </build>

目录结构

单元测试教程 - 图1

IntegrationTestConfiguration.groovy

  1. @TestConfiguration
  2. @Import(LiquibaseConfig)
  3. class IntegrationTestConfiguration {
  4. private final detachedMockFactory = new DetachedMockFactory()
  5. @Value('${choerodon.oauth.jwt.key:choerodon}')
  6. String key
  7. @Autowired
  8. TestRestTemplate testRestTemplate
  9. @Autowired
  10. LiquibaseExecutor liquibaseExecutor
  11. final ObjectMapper objectMapper = new ObjectMapper()
  12. //Mock KafkaTemplate, 以防spring-for-kafka影响报错
  13. @Bean
  14. KafkaTemplate kafkaTemplate() {
  15. detachedMockFactory.Mock(KafkaTemplate)
  16. }
  17. @PostConstruct
  18. void init() {
  19. //通过liquibase初始化h2数据库
  20. liquibaseExecutor.execute()
  21. //给TestRestTemplate的请求头部添加JWT
  22. setTestRestTemplateJWT()
  23. }
  24. private void setTestRestTemplateJWT() {
  25. testRestTemplate.getRestTemplate().setRequestFactory(new HttpComponentsClientHttpRequestFactory())
  26. testRestTemplate.getRestTemplate().setInterceptors([new ClientHttpRequestInterceptor() {
  27. @Override
  28. ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
  29. httpRequest.getHeaders()
  30. .add('JWT_Token', createJWT(key, objectMapper))
  31. return clientHttpRequestExecution.execute(httpRequest, bytes)
  32. }
  33. }])
  34. }
  35. static String createJWT(final String key, final ObjectMapper objectMapper) {
  36. Signer signer = new MacSigner(key)
  37. CustomUserDetails defaultUserDetails = new CustomUserDetails('default', 'unknown', Collections.emptyList())
  38. defaultUserDetails.setUserId(0L)
  39. defaultUserDetails.setOrganizationId(0L)
  40. defaultUserDetails.setLanguage('zh_CN')
  41. defaultUserDetails.setTimeZone('CCT')
  42. String jwtToken = null
  43. try {
  44. jwtToken = 'Bearer ' + JwtHelper.encode(objectMapper.writeValueAsString(defaultUserDetails), signer).getEncoded()
  45. } catch (IOException e) {
  46. e.printStackTrace()
  47. }
  48. return jwtToken
  49. }
  50. }

application-test.yml

  1. spring:
  2. cloud:
  3. bus:
  4. enabled: false # 关闭bus,否则kafka报错
  5. sleuth:
  6. stream:
  7. enabled: false # 关闭zipkin,否则kafka报错
  8. datasource: # 使用内存数据库h2
  9. password: sa
  10. url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=Mysql;TRACE_LEVEL_SYSTEM_OUT=2;
  11. username: sa
  12. autoconfigure: # 关闭LiquibaseAutoConfiguration和KafkaAutoConfiguration的自动化配置
  13. exclude: org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
  14. hystrix:
  15. stream:
  16. queue:
  17. enabled: false # 关闭hystrix stream,否则kafka报错
  18. data:
  19. dir: src/main/resources
  20. eureka:
  21. client:
  22. enabled: false # 关闭eureka

编写测试groovy脚本

添加注解@SpringBootTest(webEnvironment = RANDOM_PORT)和@Import(IntegrationTestConfiguration)。继承Specification且类名为Spec后缀。注入所需要测试的类对象通过given(前提条件),when(触发条件),then(期望结果)。

  1. @SpringBootTest(webEnvironment = RANDOM_PORT)
  2. @Import(IntegrationTestConfiguration)
  3. class SagaTaskMapperSpec extends Specification {
  4. @Autowired
  5. SagaTaskMapper sagaTaskMapper
  6. def 'insert'() {
  7. given: '创建一个bean'
  8. SagaTask sagaTask = new SagaTask()
  9. def testCode = 'test_code'
  10. def testSagaCode = 'test_saga_code'
  11. sagaTask.setCode(testCode)
  12. sagaTask.setSagaCode(testSagaCode)
  13. sagaTask.setSeq(1)
  14. sagaTask.setIsEnabled(true)
  15. sagaTask.setMaxRetryCount(1)
  16. when: '插入数据库'
  17. sagaTaskMapper.insert(sagaTask)
  18. then: '返回ID'
  19. sagaTask.getId() != null
  20. when: '根据ID在数据库查询'
  21. def data = sagaTaskMapper.selectByPrimaryKey(sagaTask.getId())
  22. then: '对比数据'
  23. data.getCode() == testCode
  24. data.getSagaCode() == testSagaCode
  25. data.getSeq() == 1
  26. data.getIsEnabled()
  27. data.getMaxRetryCount() == 1
  28. }
  29. }

其他

如果在idea不能直接编译运行测试groovy文件,可能是因idea没有识别test/groovy目录。

单元测试教程 - 图2

spock其他用法

  • @Shared:多个测试方法中共享数据

  • @Stepwise: 当测试方法间存在依赖关系时,标明测试方法将严格按照其在源代码中声明的顺序执行

  • setupSpec(): 设置每个测试类的环境

  • setup(): 设置每个测试方法的环境,每个测试方法执行一次

  • cleanup(): 清理每个测试方法的环境,每个测试方法执行一次

  • cleanupSepc(): 清理每个测试类的环境

  • @Ignore: 忽略测试方法

  • @IgnoreRest:只测试这个方法,而忽略所有其他方法

  • @Timeout: 设置测试方法的超时时间,默认单位为秒

参考

https://blog.csdn.net/u012129558/article/details/78675421

http://blog.codepipes.com/testing/spock-vs-junit.html