测试框架spock

spock相关定义:

语义块

  • given,setup:初始消息创建(setup is an alias of thg given);
  • when:触发测试动作
  • then:验证测试结果
  • expect:简单版本的then
  • where:参数流测试
  • cleanup:类似finally,清理资源
  • and:续上一个语句块,(出现在given后面就是given块,出现在then后面就是then块,个人理解只是为了分割逻辑让代码清淅存在的)

方法:

  • setup(),在每一个测试方法执行前执行
  • setupSpec(),在测试方法执行前执行,只执行一次
  • cleanup(), 在每一个测试方法执行后执行
  • cleanupSpec(),所有测试方法执行结束后执行
  • 语法糖方法:
    • old(),取when或expect之前的数据,主要用于测试前后对比

注解:

  • 注释类注解
    • @Subject(Class) 用于标记正在测试的类,也可以直接写在类初始化语句上,仅影响可读性
    • @Narrative(“““balabalabala”"") Longer description of test
    • @Title(“test”),标题
    • @Issue(),用来标解决哪个问题
    • @See(),同上,用来写连接
    • 惊,居然写了这么多用来解释这个测试是干啥的注解
  • 其他注解
    • @Shared 标记一个对象是共享的,只初始化一次
    • @Unroll 每一个where输入都做为一个单独的测试执行
    • @AutoCleanup(“close”) 自动执行清理方法,默认是close()方法
    • @Ignore 忽略此测试和Junit的 Ignore 一致
    • @IgnoreIf()/@Requires 如果满足条件就忽略/导入,需传一个predicate Closure {} (谓词闭包?)
    • @Timeout(int) 方法超时时间
    • @Stepwise 严格按照方法编写顺序测试,如果前面方法失败,将跳过后面方法,方法间有依赖关系的话助于避免方法失败后的连续错误

断言

  • spock 在then和expect中,默认断言所有返回为boolean型的语句
  • 如果需要在其他语句块里断言,可以显示的 assert
  • 异常断言可以用thrown
  • 验证没有抛出某种异常可以用notThown
    given:
       def stack = new Stack()
       assert stack.empty
    when:
       stack.pop()
    then:
       null != stack
       def e = thrown(EmptyStackException)
       e.printStack()
       notThown(NullPointerException)
    

MOCK/Stub/Spy

  • MOCK/Stub/Spy 这部分单独写一个文档

测试编写

  • 测试类需要继承Specification类
  • 测试类为groovy类,而非java类
  • given,when,then 测试流,when和then需要搭配使用,
    • given:给写测试数据
    • when:执行待测试的函数
    • then:校验结果是否符合预期
    class MathTest extends Specification{
       def "math.max test"(){
          given:
             def a= 3
             def b= 5
          when:
             def c = Math.max(a, b)
          then:
             c == 5
    }
    }
    
  • given,expect,where 测试流,适用于多个条件测试
    • expect 是简写的(when then),通过boolen类断言来校验结果
    • where 语义块必须放在expect后面,不能放在(when,then)后面
    • 如果觉得expect不分执行测试和校验结果,可以用and将执行的校验分开
       @Unroll
       def "maximum of #a and #b should be #c"() {
          expect:
             resutl = Math.max(a, b)
          and:
             result == c
          where:
             a | b || c
             3 | 5 || 5
             7 | 0 || 7
             0 | 0 || 0
          }
    

springboot集成

maven引入

  • springBoot
    <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spock-spring</artifactId>
          <!-- 版本不建议写,由springboot指定,不然会报莫名的错
          <version>1.0-groovy-2.4</version>
             -->
          <scope>test</scope>
       </dependency>
    
  • *非springBoot,sprigBoot一般不需要独立引入,避免报错
    <dependency>
          <groupId>org.spockframework</groupId>
          <artifactId>spock-core</artifactId>
          <version>1.3-groovy-2.5</version>
          <scope>test</scope>
       </dependency>
       <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>2.5.13</version>
          <scope>test</scope>
    </dependency>
    

基础类

  • springboot集成测试基础类,实际写的时候继承这个类
    @WebAppConfiguration
    @ContextConfiguration(classes = Application.class)
    @SpringBootTest
    @Slf4j
    class BaseTestSpock extends Specification{}
    
  • 注意:不要在基础接口里写@RunWith(SpringRunner.class),因为会覆盖spock的runwith导致spock无法正常运行
  • 如果是单独测一部分功能,比如仅测一个service,可以在启动类里,仅扫描dao层包,然后@import(Serivce.class),然的就可以autowaried这个类了 如仅启动dao层的启动类
    @SpringBootApplication(scanBasePackages = {"com.*.*.dao.*"})
    //去除swagger
    @EnableAutoConfiguration(exclude = SwaggerAutoConfiguration.class)
    @MapperScan("com.*.*billing*.dao.mapper")
    public class ApplicationTest{
       public static void main(String[] args) {
          SpringApplication.run(ApplicationTest.class, args);
       }
    }
    
  • 测试单个service
    @SpringBootTest
    @ContextConfiguration(classes = ApplicationTest.class)
    //导入单个service
    @Import(MealServiceImpl.class)
    class MealDaoTest extends Specification {
       @Autowired
       MealServiceImpl mealService
       def "test listMeal"() {
          given:
             ListMealDTO listMealDTO = new ListMealDTO()
             listMealDTO.setUuid("3423fsfrsfsd")
          when:
             def resultList = mealService.listMeal(listMealDTO)
          then:
             resultList.isEmpty()
       }
    }
    

tips

  • idea设置groovy的Label indent缩进改为4(默认0),看起来更舒服
  • groovy3.0 以下版本不支持 java8的 lambda语法,需要转为Closure 方法,不然后报错,如下面java:
    list.stream().filter(x->!x.getDisabled()).count() > 1;
    

    需要转为groovy的闭包写法

    list.stream().filter({x->!x.getDisabled()}).count() > 1
    
  • end