网站首页 文章专栏 今天写点soul单元测试相关的- soul全局异常处理单元测试
单元测试是一种软件测试方法,通过该方法可以测试源代码的各个单元以确定它们是否适合使用。单元是最小的可测试软件组件,它通常执行单个内聚功能。单元测试就是是指对这个最小可测试组件——即单元进行检查和验证
单元测试拥有保证代码质量、尽早发现软件Bug、简化调试过程、促进变化并简化集成、使流程更灵活等优势
代码覆盖率:代码的覆盖程度,一种度量方式。
代码覆盖程度的度量方式是有很多种的,这里介绍一下最常用的几种:
1). 语句覆盖(StatementCoverage)
这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了,这里说的是“可执行语句”,只统计能够执行的代码被执行了多少行。需要注意的是,单独一行的花括号{} 也常常被统计进去。语句覆盖常常被人指责为“最弱的覆盖”,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等
2). 判定覆盖(DecisionCoverage)
又称分支覆盖(BranchCoverage),所有边界覆盖(All-EdgesCoverage),基本路径覆盖(BasicPathCoverage),判定路径覆盖(Decision-Decision-Path)。它度量程序中每一个判定的分支是否都被测试到了。这句话是需要进一步理解的,应该非常容易和下面说到的条件覆盖混淆。因此我们直接介绍第三种覆盖方式,然后和判定覆盖一起来对比,就明白两者是怎么回事了。
3). 条件覆盖(ConditionCoverage)
它度量判定中的每个子表达式结果true和false是否被测试到了。为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如我们的被测代码如下:
4). 路径覆盖(PathCoverage)
又称断言覆盖(PredicateCoverage)。它度量了是否函数的每一个分支都被执行了。 这句话也非常好理解,就是所有可能的分支都执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,可想而知,测试路径随着分支的数量指数级别增加
举例:
int foo( int a, int b){
int nReturn = 0 ;
if (a < 10 ){ // 分支一
nReturn += 1 ;
}
if (b < 10 ){ // 分支二
nReturn += 10 ;
}
return nReturn;
}对上面的代码,我们分别针对我们前三种覆盖方式来设计测试案例:
1>. 语句覆盖
TestCase a = 5, b = 5 nReturn = 11 语句覆盖率100%
2>. 判定覆盖
TestCase1 a = 5, b = 5 nReturn = 11 TestCase2 a = 15, b = 15 nReturn = 0 判定覆盖率100%
3>. 条件覆盖
TestCase1 a = 5, b = 15 nReturn = 1 TestCase2 a = 15, b = 5 nReturn = 10 条件覆盖率100%
4>. 路径覆盖
TestCase1 a = 5, b = 5 nReturn = 0 TestCase2 a = 15, b = 5 nReturn = 1 TestCase3 a = 5, b = 15 nReturn = 10 TestCase4 a = 15, b = 15 nReturn = 11 路径覆盖率100%
路径覆盖将所有可能的返回值都测试到了。这也正是它被很多人认为是“最强的覆盖”的原因了。
还有一些其他的覆盖方式,如:循环覆盖(LoopCoverage),它度量是否对循环体执行了零次,一次和多余一次循环。剩下一些其他覆盖方式就不介绍了。
1. 不应该编写成功通过的单元测试-它们应该被写成不通过的。你可以在几分钟内让任何一组测试通过,但这只是在欺骗你自己。
2. 测试类应该只测试一个功能-你应该用一个功能去测试一个方法。否则,你会违反了单一职责原则。
3. 测试类具备可读性-确保测试类标有注释并且容易理解,就像其他的代码一样。
4. 良好的命名规范-再次测试时应该像其他代码一样-便于人们理解。
5. 把断言从行为中分离出来-你的断言应该用来检验结果,而不是执行逻辑操作的。
6. 使用具体的输入-不要使用任何的自动化测试数据来输入,像date()这些产生的数据会引入差异。
7. 把测试类分类,放在不同的地方-从逻辑的角度看,当没有错误指向特定的问题时这更容易去查找。
8. 好的测试都是一些独立的测试类-你应该让测试类与其他的测试、环境设置等没有任何依赖。这利于创建多个测试点。
9. 不要包含私有的方法-他们都是一些具体的实现,不应该包含在单元测试里。
10. 不要连接数据库或者数据源-这是不靠谱的。因为你不能确保数据服务总是一样的并且能够创建测试点。
11. 一个测试不要超过一个模拟(mock对象)-我们努力去消除错误和不一致性。
12. 单元测试不是集成测试-如果你想测试结果,不要使用单元测试。
13. 测试必须具有确定性-你需要一个确定的预测结果,所以,如果有时候测试通过了,但是不意味着完成测试了。
14. 保持你的测试是幂等的-你应该能够运行你的测试多次而不改变它的输出结果,并且测试也不应该改变任何的数据或者添加任何东西。无论是运行一次还是一百万次,它的效果都应该是一样的。
15. 测试类一次仅测试一个类,测试方法一次仅测试一个方法-组织方法能够在问题出现时检测出来,并帮你确定测试依赖。
16. 在你的测试里使用异常-你在测试里会遇到异常,所以,请不要忽略它,要使用它。
17. 不要使用你自己的测试类去测试第三方库的功能-大多数好的库都应该有它们自己的测试,如果没考虑用mocks去产生一致性的结果的话。
18. 限制规则-当在一些规则下写测试时,记住你的限制和它们(最小和最大)设置成最大的一致性。
19. 测试类不应该需要配置或者自定义安装-你的测试类应该能够给任何人使用并且使它运行。“在我的机器上运行”不应该出现在这。
我要测试的是GlobalErrorHandler,该类是对全局异常做出统一格式日志打印,以及返回参数处理
GlobalErrorHandler 继承了 DefaultErrorWebExceptionHandler,重写了3个方法,要测试的也是这三个
@Override
protected Map<String, Object> getErrorAttributes(final ServerRequest request, final boolean includeStackTrace) {
logError(request);
return response(request);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected int getHttpStatus(final Map<String, Object> errorAttributes) {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}首先新建测试类,构建待测试的对象:
@RunWith(MockitoJUnitRunner.class)
public class GlobalErrorHandlerTest {
private GlobalErrorHandler globalErrorHandler;
...
}使用before注解,准备测试环境
@Before
public void setUp() {
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
SpringBeanUtils.getInstance().setCfgContext(context);
when(context.getBean(SoulResult.class)).thenReturn(new DefaultSoulResult() { });
ErrorAttributes errorAttributes = new DefaultErrorAttributes();
ResourceProperties resourceProperties = mock(ResourceProperties.class);
ErrorProperties serverProperties = mock(ErrorProperties.class);
ApplicationContext applicationContext = new AnnotationConfigReactiveWebApplicationContext();
ViewResolver viewResolver = mock(ViewResolver.class);
globalErrorHandler = new GlobalErrorHandler(errorAttributes, resourceProperties, serverProperties, applicationContext);
globalErrorHandler.setViewResolvers(Collections.singletonList(viewResolver));
}此处有巨坑,DefaultErrorAttributes对象不能 mock()出来,mock出来的对象在实际测试过程中,一直报nullpointexcept,new出来就可以了。
还有测试的方法中使用了其他的工具类,工具类有依赖别的静态方法,需要提前mock好
@Test
public void getErrorAttributes() {
ServerWebExchange webExchange =
MockServerWebExchange.from(MockServerHttpRequest
.post("http://localhost:8080/favicon.ico"));
MockServerRequest serverRequest = MockServerRequest.builder()
.exchange(webExchange)
.attribute("org.springframework.boot.web.reactive.error.DefaultErrorAttributes.ERROR", new NullPointerException("errorMessage"))
.build();
Map<String, Object> response = globalErrorHandler.getErrorAttributes(serverRequest, false);
Assert.assertNotNull(response);
Assert.assertEquals(response.get("code"), (long) HttpStatus.INTERNAL_SERVER_ERROR.value());
Assert.assertEquals(response.get("message"), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
Assert.assertEquals(response.get("data"), "errorMessage");
}
对重写的getErrorAttributes方法进行测试,构建web请求,并在请求中模拟异常,并对结果进行断言
其他方法类似,写完本地运行下,没问题,再检查下checkstyle,就可以提交了
我提交后,发现github的CI没过,发现原来是apache 的license没写,加上后就行了,然而尴尬的是,覆盖率下降了。。。
明天再改改吧
版权声明:本文由星尘阁原创出品,转载请注明出处!