Spock单元测试框架实战指南之异常测试

发表于:2020-12-03 09:45

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Java老K    来源:博客园

  背景
  有些方法需要抛出异常来中断或控制流程,比如参数校验的逻辑: 不能为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方式:
  @Rule
  public [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是否符合预期值。
  另外在where标签里构造请求参数时调用的getUser()方法使用了groovy的闭包功能,即case里面的condition1,condition2的写法。
  groovy的闭包(closure) 类似Java的lambda表达式,这样写主要是为了复用之前的请求参数,所以使用了闭包,当然也可以使用传统的new对象之后,setXXX的方式构造请求对象。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号