百日筑基第六十三天-使用Groovy+Spock写单测
要使用Groovy+Spock编写单测,首先引入如下Maven依赖,同时安装Groovy插件。
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
</dependency>
接下来我们来讲一下Groovy的常见语法,作为一门动态语言,它的写法比Java简单很多,而且基本只要你会写Java,你很容易就可以上手Groovy。
首先看一下测试模板方法的定义和JUnit的对比:
Spock的模板方法说明:
def setupSpec() {} // runs once - before the first feature method
def setup() {} // runs before every feature method
def cleanup() {} // runs after every feature method
def cleanupSpec() {} // runs once - after the last feature method
常见用法
expect-where
有时,里面的某个测试用例失败了,却难以查到是哪个失败了。这时候,可以使用Unroll注解,该注解会将where子句的每个测试用例转化为一个 @Test 独立测试方法来执行,这样就很容易找到错误的用例。 方法名还可以更可读些。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class StaffQueryServiceSpec extends Specification {
@Autowired
private StaffQueryService staffQueryService;
@Unroll
def "通过归属人ID查询员工"(){
expect:
staffQueryService.getStaffById(ownerId)!=null
where:
ownerId | _
1 | _
2 | _
}
}
where后面的条件可以通过表格的形式或者数据管道的形式,单个数据的时候可以通过“_”构建表格
def "通过归属人ID查询员工"(){
expect:
staffQueryService.getStaffById(ownerId)!=null
where:
ownerId << [1,2,3]
}
given-expect-where
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderQueryServiceSpec extends Specification {
@Autowired
private OrderQueryService orderQueryService;
@Unroll
def "根据经销商机构判断是否存在在途订单"() {
given:
def orgId = 1
def dealerId = dealer
expect:
orderQueryService.hasOnWayOrder(dealerId, orgId) == result
where:
dealer | result
1 | true
9999 | false
}
}
given-when-then
这个比较符合敏捷开发中用户故事的描述,也是用的最多的一种写法。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderAppServiceSpec extends Specification {
@Autowired
private OrderAppService orderAppService;
def "取消订单"() {
given:
def dto = new CancelOrderDTO(operatorId: "1", operatorName: "tzx", dealerId: 1)
when:
dto.setOrderNo("OR202001020014")
orderAppService.cancelOrder(dto)
then:
noExceptionThrown()
when:
dto.setOrderNo("OR202001020010")
orderAppService.cancelOrder(dto)
then:
thrown(Exception)
}
}
given-when-then-where
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderAppServiceSpec extends Specification {
@Autowired
private OrderAppService orderAppService;
def "取消订单"() {
given:
def dto = new CancelOrderDTO(operatorId: operatorId, operatorName: operatorName, dealerId: dealerId)
when:
dto.setOrderNo("OR202001020014")
orderAppService.cancelOrder(dto)
then:
noExceptionThrown()
where:
operatorId| operatorName | dealerId
1| "tzs" | 1
2| "zhangsan" | 1
3 | "wangwu" | 2
}
}
如果有多个条件需要测试时可以采用这种写法
given-when-then-thrown
在 when 子句中调用了会抛出异常的方法,而在 then 子句中,使用 thrown 接收方法抛出的异常,并赋给指定的变量 ex, 之后就可以对 ex 进行断言了。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class CustomerAppServiceSpec extends Specification {
@Autowired
private CustomerAppService customerAppService;
def "更新未存在客户" (){
given:
def dto = new CustomerCreateDTO(customerId: -1,customerName: "小王");
when: "客户不存在时"
customerAppService.updateCustomer(dto);
then:
def ex = thrown(ConnectorBusinessException)
ex.class.name = "java.lang.RuntimeException"
ex.cause.class.name = "com.souche.connector.common.exception.ConnectorBusinessException"
}
}
打桩
很多时候我们测试的时候不需要执行真正的方法,因为在测试代码里面构建一个真实的对象是比较麻烦的,特别是使用一些依赖注入框架的时候,因此有了打桩的功能。
比如我们依赖某个外部应用的service的返回值,可以采用如下的方式。
@SpringBean
UserCenterQueryService userCenterQueryService = Mock{
getUserByShopCodeAndRole(_,_) >> "001"
};
如果需要定义多次调用返回不同的返回值可以采用下面的方式
@SpringBean
UserCenterQueryService userCenterQueryService = Mock{
getUserByShopCodeAndRole(_,_) >>> ["001","002"]
};