测试抛出异常_Spock单元测试框架实战指南四 - 异常测试

本文深入探讨了如何使用Spock测试框架测试代码中抛出的异常,特别是针对自定义业务异常APIException的验证。通过使用Spock的thrown()方法,结合where表格功能,可以方便地覆盖多种异常情况,简化测试代码编写。文中还介绍了闭包在构造请求参数时的作用。
摘要由CSDN通过智能技术生成

这一篇主要讲使用Spock如何测试代码中抛异常的场景

背景

有些方法需要抛出异常来中断或控制流程,比如参数校验的逻辑: 不能为null,不符合指定的类型,list不能为空等验证,如果校验不通过则抛出checked异常,这个异常一般都是我们封装的业务异常信息,比如下面的业务代码:

/** * 校验请求参数user是否合法 * @param user * @throws APIException */public void validateUser(UserVO user) throws APIException {    if(user == null){        throw new APIException("10001", "user is null");    }    if(null == user.getName() || "".equals(user.getName())){        throw new APIException("10002", "user name is null");    }    if(user.getAge() == 0){        throw new APIException("10003", "user age is null");    }    if(null == user.getTelephone() || "".equals(user.getTelephone())){        throw new APIException("10004", "user telephone is null");    }    if(null == user.getSex() || "".equals(user.getSex())){        throw new APIException("10005", "user sex is null");    }    if(null == user.getUserOrders() || user.getUserOrders().size() <= 0){        throw new APIException("10006", "user order is null");    }    for(OrderVO order : user.getUserOrders()) {        if (null == order.getOrderNum() || "".equals(order.getOrderNum())) {            throw new APIException("10007", "order number is null");        }        if (null == order.getAmount()) {            throw new APIException("10008", "order amount is null");        }    }}

APIException是我们封装的业务异常,主要包含errorCode,errorMessage属性:

/** * 自定义业务异常 */public class APIException extends RuntimeException {    private String errorCode;    private String errorMessage;    setXXX...    getXXX...}

这个大家应该都很熟悉,针对这种抛出多个不同错误码和错误信息的异常,如果我们使用Junit的方式测试,会比较麻烦,就目前我使用过的方法,如果是单个异常还好,多个的就不太好写测试代码

最常见的写法可能是下面这样:

@Test public void testException() {  UserVO user = null;  try {    validateUser(user);  } catch (APIException e) {    assertThat(e.getErrorCode(), "10001");    assertThat(e.getErrorMessage(), "user is null");  }  UserVO user = new UserVO();  try {    validateUser(user);  } catch (APIException e) {    assertThat(e.getErrorCode(), "10002");    assertThat(e.getErrorMessage(), "user name is null");  }  ...}

当然可以使用junit的ExpectedException方式:

@Rulepublic [ExpectedException](http://javakk.com/tag/expectedexception "查看更多关于 ExpectedException 的文章") exception = ExpectedException.none();exception.expect(APIException.class); // 验证抛出异常的类型是否符合预期exception.expectMessage("Order Flight return null exception"); //验证抛出异常的错误信息

或者使用@Test(expected = APIException.class) 注解

但这两种方式都有缺陷:

@Test方式不能指定断言的异常属性,比如errorCode,errorMessage

ExpectedException的方式也只提供了expectMessage的api,对自定义的errorCode不支持,尤其像上面的有很多分支抛出多种不同异常码的情况

thrown

我们来看下Spock是如何解决的,Spock内置thrown()方法,可以捕获调用业务代码抛出的预期异常并验证,再结合where表格的功能,可以很方便的覆盖多种自定义业务异常,代码如下:

/** * 校验用户请求参数的测试类 * @author 公众号:Java老K * 个人博客:www.javakk.com */class UserControllerTest extends Specification {    def userController = new UserController()    @Unroll    def "验证用户信息的合法性: #expectedMessage"() {        when: "调用校验用户方法"        userController.validateUser(user)        then: "捕获异常并设置需要验证的异常值"        def exception = thrown(expectedException)        exception.errorCode == expectedErrCode        exception.errorMessage == expectedMessage        where: "表格方式验证用户信息的合法性"        user           || expectedException | expectedErrCode | expectedMessage        getUser(10001) || APIException      | "10001"         | "user is null"        getUser(10002) || APIException      | "10002"         | "user name is null"        getUser(10003) || APIException      | "10003"         | "user age is null"        getUser(10004) || APIException      | "10004"         | "user telephone is null"        getUser(10005) || APIException      | "10005"         | "user sex is null"        getUser(10006) || APIException      | "10006"         | "user order is null"        getUser(10007) || APIException      | "10007"         | "order number is null"        getUser(10008) || APIException      | "10008"         | "order amount is null"    }    def getUser(errCode) {        def user = new UserVO()        def condition1 = {            user.name = "杜兰特"        }        def condition2 = {            user.age = 20        }        def condition3 = {            user.telephone = "15801833812"        }        def condition4 = {            user.sex = "男"        }        def condition5 = {            user.userOrders = [new OrderVO()]        }        def condition6 = {            user.userOrders = [new OrderVO(orderNum: "123456")]        }        switch (errCode) {            case 10001:                user = null                break            case 10002:                user = new UserVO()                break            case 10003:                condition1()                break            case 10004:                condition1()                condition2()                break            case 10005:                condition1()                condition2()                condition3()                break            case 10006:                condition1()                condition2()                condition3()                condition4()                break            case 10007:                condition1()                condition2()                condition3()                condition4()                condition5()                break            case 10008:                condition1()                condition2()                condition3()                condition4()                condition5()                condition6()                break        }        return user    }}

主要代码就是在"验证用户信息的合法性"的测试方法里,其中在then标签里用到了Spock的thrown()方法,这个方法可以捕获我们要测试的业务代码里抛出的异常

thrown方法的入参expectedException,是我们自己定义的异常变量,这个变量放在where标签里就可以实现验证多种异常情况的功能(intellij idea格式化快捷键可以自动对齐表格)

expectedException的类型是我们调用的validateUser方法里定义的APIException异常,我们可以验证它的所有属性,errorCode、errorMessage是否符合预期值

abe267d656297a0ebaa96c89f3c3d82c.png

另外在where标签里构造请求参数时调用的getUser()方法使用了groovy的闭包功能,即case里面的condition1,condition2的写法

groovy的闭包(closure) 类似Java的lambda表达式,这样写主要是为了复用之前的请求参数,所以使用了闭包,当然也可以使用传统的new对象之后,setXXX的方式构造请求对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值