3年前 (2021-06-15)  Spock系列 |   抢沙发  1685 
文章评分 0 次,平均分 0.0

叫"高级语法"有点言过其实了,标题党嫌疑。主要是针对我们业务代码中一些比较特殊的场景可以考虑使用。

先简单说下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

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册