叫"高级语法"有点言过其实了,标题党嫌疑。主要是针对我们业务代码中一些比较特殊的场景可以考虑使用。
先简单说下Groovy的闭包概念,官方定义:
Groovy中的闭包是一个开放,匿名的代码块,可以接受参数,返回值并分配给变量
语法如下:
{ [closureParameters -> ] statements }
其中[closureParameters->]
代表参数,多个参数用逗号分割,用->
隔开参数与方法体,没有参数可以不写->
常用的写法如下:
{ printf 'Hello World' } //打印 Hello World
{ println it } //闭包有默认参数it,可以不用申明
{ name -> println name } // 定义一个name参数
简而言之,类似于Java中的lambda表达式(但比lambda要高级一些,比如委托代理功能),本篇文章主要目的是介绍如何在单元测试中使用闭包。
考虑下面的业务场景:这个方法的功能是通过soaClient
调用接口后获取返回数据,然后进行结果转换,得到response
后再做其他的业务逻辑,但是调用的接口是非核心接口,报错不会影响主流程,所以通过try/catch
处理,然后继续剩下的流程,伪代码如下:
public Response process(Request request) {
Response response = null;
try {
SOAResponse soaResponse = soaClient.invoke(request); // 调用接口
if (soaResponse != null) {
response = new Response();
response.setResultCode(soaResponse.isSuccess() ? 0 : -1);
response.setResultMsg(soaResponse.getResultMsg());
}
} catch (SOAException ex) {
logger.error(ex);
}
// 其他业务逻辑
if(response != null) {
if (response.getResultCode() == 0) {
}
if (response.getResultCode() == 1) {
}
...
}
return response;
}
正常我们测试这个方法,更多关注的是正常业务流程是否符合预期,比如说soaResponse
返回结果是成功还是失败,然后catch
外的剩余代码在继续其他逻辑判断,但是有一种特殊情况就是"soaClient.invoke
"接口抛异常了,比如TimeOutException
,那么下面的代码"if (soaResponse != null) {
"就不会执行了,这种情况如果我们不测试的话,catch
下面剩余的代码可能会有问题(没有考虑response == null
的特殊场景)。
所以针对这种情况,单测最好也要能测试到这种用例,可以使用mock的方式让"soaClient.invoke
"抛出一个异常出来,验证下上面说的这种case。
如果希望能在where
块里既验证正常的业务逻辑又验证异常的逻辑,这里就可以通过使用闭包的方式实现,如下:
@Unroll
def process() {
given: "设置mock场景"
soaClient.invoke(_) >> { soaResponse() } // { soaResponse.call() } 的简化写法
when: "调用被测方法"
def response = handler.process(new Request())
then: "设置需要验证的属性"
response?.resultCode == status // ?语法类似于.Net或kotlin的空指针安全,为null则不调用resultCode属性
where: "验证不同业务场景"
soaResponse | status
getSoaResponse(0) | 0
getSoaResponse(1) | 1
getSoaResponse(-1) | null // 这个case就是测试接口抛异常是否影响后续流程
}
def getSoaResponse(int index) {
def soaResponse = {} // 声明一个闭包
switch (index) {
case 0:
soaResponse = { new SOAResponse("success": true) }
break
case 1:
soaResponse = { new SOAResponse("success": false) }
break
case -1:
soaResponse = { throw new SOAException("接口异常") } // 如果不使用闭包的话,Spock编译到这里会直接抛出异常
break
}
return soaResponse
}
在given
标签里设置的mock值:" { soaResponse() }
",这个就是groovy里闭包的写法,soaResponse
变量的值定义在where
: "验证不同业务场景" 的第一列,每个用例通过调用getSoaResponse()
生成,
getSoaResponse()
方法返回的也是一个闭包,这样做的原因是因为"case:-1
"是抛出一个SOAException
异常,而不是正常的构造数据,如果不使用闭包的方式,Spock编译到这里直接就抛出这个异常了!
而"soaClient.invoke(_) >> { soaResponse() }
" 这里加的花括号是表示只有在业务代码执行到"soaClient.invoke(_)
" 方法时才执行闭包里的逻辑,所以这里使用了嵌套闭包。
当然也可以针对这个异常case单独写一个单测,不放在原来的where里也可以,如果觉得闭包的语法不好理解的话。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2013.html
暂无评论