《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(9) https://developer.aliyun.com/article/1232106?groupCode=java
3. 验证抛出异常问题
这里,以创建用户时抛出异常为例,来说明验证抛出异常时所存在的问题。
代码案例:
1) 不验证抛出异常类型
反面案例:
在验证抛出异常时,很多人使用@Test注解的expected属性,并且指定取值为Exception.class,主要原因是:
• 单元测试用例的代码简洁,只有一行@Test注解;
• 不管抛出什么异常,都能保证单元测试用例通过。
存在问题:
上面用例指定了通用异常类型,没有对抛出异常类型进行验证。所以,如果把ExampleException异常改为RuntimeException异常,该单元测试用例是无法验证出来的。
2) 不验证抛出异常属性
反面案例:
既然需要验证异常类型,简单地指定@Test注解的expected属性为ExampleException.class即可。
存在问题:
上面用例只验证了异常类型,没有对抛出异常属性字段(异常消息、异常原因、错误编码等)进行验证。所以,如果把错误编码DATABASE_ERROR(数据库错误)改为PARAMETER_ERROR(参数错误),该单元测试用例是无法验证出来的。
3) 只验证抛出异常部分属性
反面案例:
如果要验证异常属性,就必须用Assert.assertThrows方法捕获异常,并对异常的常用属性进行验证。但是,有些人为了偷懒,只验证抛出异常部分属性。
存在问题:
上面用例只验证了异常类型和错误编码,如果把错误消息“创建用户异常”改为“创建用户错误”,该单元测试用例是无法验证出来的。
4) 不验证抛出异常原因
反面案例:
先捕获抛出异常,再验证异常编码和异常消息,看起来很完美了。
存在问题:
通过代码可以看出,在抛出ExampleException异常时,最后一个参数e是我们模拟的userService.createUser方法抛出的RuntimeException异常。但是,我们没有对抛出异常原因进行验证。如果修改代码,把最后一个参数e去掉,上面的单元测试用例是无法验证出来的。
5) 不验证相关方法调用
反面案例:
很多人认为,验证抛出异常就只验证抛出异常,验证依赖方法调用不是必须的。
存在问题:
如果不验证相关方法调用,如何能证明代码走过这个分支?比如:我们在创建用户之前,检查用户名称无效并抛出异常。
6) 完美地验证抛出异常
一个完美的异常验证,除对异常类型、异常属性、异常原因等进行验证外,还需对抛出异常前的依赖方法调用进行验证。
完美案例:
4. 验证抛出异常准则
1) 必须验证所有抛出异常
在单元测试中,必须验证所有抛出异常:
• 来源于属性字段的判断
• 来源于输入参数的判断
• 来源于返回值的判断
• 来源于模拟方法的调用
• 来源于静态方法的调用
具体内容可以参考《抛出异常来源方式》章节。
2) 必须验证异常类型、异常属性、异常原因
在验证抛出异常时,必须验证异常类型、异常属性、异常原因等。
正例:
反例:
3) 验证抛出异常后,必须验证相关方法调用
在验证抛出异常后,必须验证相关方法调用,来保证单元测试用例走的是期望分支。
正例:
《Java单元测试实战》——无效单测:那些年,我们写过的无效单元测试(11) https://developer.aliyun.com/article/1232104?groupCode=java