1 sonar 概述

sonar 是什么?

  • Sonar 是一个用于代码质量管理的开放平台。通过插件机制,Sonar 可以集成不同的测试工具,代码分析工具,以及持续集成工具。

  • 持续集成工具(例如 Hudson/Jenkins 等)不同,Sonar 并不是简单地把不同的代码检查工具结果(例如 FindBugsPMD 等)直接显示在 Web 页面上,而是通过不同的插件对这些结果进行再加工处理,通过量化的方式度量代码质量的变化,从而可以方便地对不同规模和种类的工程进行代码质量管理

  • 在对其他工具的支持方面,Sonar 不仅提供了对 IDE 的支持,可以在 Eclipse 和 IntelliJ IDEA 这些工具里联机查看结果;同时 Sonar 还对大量的持续集成工具提供了接口支持,可以很方便地在持续集成中使用 Sonar。

1.2 Sonar 插件

1.2.1 SonarLint in IDEA

  • IDEA 中的插件叫: SonarLint
安装方式: IDEA > File > Settings > Plugins > "SonarLint"

插件配置路径: IDEA > File > Settings > Tools > SonarLint

2 sonar 常见问题及修复思路

2.1 空指针

  • 问题描述

A "NullPointerException" could be thrown; "localAddress" is nullable here.

问题代码[样例]

// 本地(服务器本机)信息
InetSocketAddress localAddress = null;
localAddress = request.getLocalAddress();
String localHostname = null;
localHostname = localAddress.getHostName();
if(StringUtils.isEmpty(localHostname)){
localHostname = "";
}
if(localAddress != null){
log.debug(String.format("[4] request-local-host-info: <host:%s, port:%s>", localHostname, localAddress.getPort()));// <host:0:0:0:0:0:0:0:1, port:18100>
}
  • 修复思路

先判断或者先实例化,再访问里面的属性或者成员。

修复代码[样例]

// 本地(服务器本机)信息
InetSocketAddress localAddress = null;
String localHostname = null;
localAddress = request.getLocalAddress();
if(!ObjectUtils.isEmpty(localAddress)){
localHostname = localAddress.getHostName();
log.debug(String.format("[4] request-local-host-info: <host:%s, port:%s>", localHostname, localAddress.getPort()));// <host:0:0:0:0:0:0:0:1, port:18100>
}
if(StringUtils.isEmpty(localHostname)){
localHostname = "";
}

2.2 Optional...orElseThrow()

  • 问题描述

The return value of orElseThrow must be used.(必须使用orElseThrow的返回值。)

问题代码[样例]

Optional.ofNullable(object).orElseThrow(() -> {
return new BusinessException("AuthRequestParametersValidator Oject is null!");
});
  • 修复思路

我假设这是一个警告(不使用orElseThrow()返回的值不应该是一个错误)。如果您希望消除该警告,请使用isPresent()代替:

if (!itemList.stream().filter(i->orderItemId.equals(i.getId())).findAny().isPresent()) {
throw new BadRequestException("12345","Item Not Found");
} // 或只是避免使用Optional s,而是使用anyMatch()代替:
if (!itemList.stream().anyMatch(i->orderItemId.equals(i.getId()))) {
throw new BadRequestException("12345","Item Not Found");
}

修复代码[样例]

if(!Optional.ofNullable(object).isPresent()){
throw new BusinessException("AuthRequestParametersValidator Oject is null!");
}

2.3 Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.

  • 问题描述

Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation. (添加一个嵌套注释,解释为什么这个方法是空的,抛出一个UnsupportedOperationException异常,或者完成实现。)

  • 修复代码(样例)

修复后:

4 浅谈:代码质量指标统计

4.1 代码覆盖率

代码覆盖率常被用来作为衡量单元测试好坏的指标。于是,测试人员费尽心思设计案例覆盖代码。用代码覆盖率来衡量,有利也有有弊。本文我们就代码覆盖率展开讨论。

4.1.1 语句覆盖/行覆盖

语句覆盖又称行覆盖段覆盖基本块覆盖。这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了。

可执行语句不包括头文件声明代码注释空行等等。

只统计可执行的代码被执行了多少行。需要注意的是,单独一行的花括号{}也常常被统计进去。

语句覆盖只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。因此测试效果不明显,很难发现代码中的问题。

这里举个栗子,我们看以下被测试代码:

int foo(int a, int b){ return a / b;}

假如我们的测试人员编写如下测试用例:

TeseCase: a = 10, b = 5

Hmm,你可以很自豪地说代码覆盖率达到了100%,并且所有测试用例都Pass了。然鹅我们却没有发现最简单的Bug(当b=0时,会抛出一个除零异常)。

因此,只用语句覆盖率很容易达到所谓的覆盖率,但是并不能保证代码质量。这也反映了几个问题:

  • 只使用语句覆盖率来衡量代码质量有问题。
  • 测试的目标是保证代码质量,单一的语句覆盖很难做到。
  • 是否应该采用更好的测试方法来保证代码质量?

4.1.2 判定覆盖/分支覆盖

判定覆盖又称分支覆盖,所有边界覆盖,基本路径覆盖,判定路径覆盖。

它度量程序中每一个判定的分支是否都被测试到了。判定覆盖非常容易和下面说到的条件覆盖混淆。

因此,我们直接和条件覆盖一起来对比,就明白两者是怎么回事了。

4.1.3 条件覆盖

它度量判定中的每个子表达式结果true和false是否被测试到了。为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如我们的被测代码如下:

int foo(int a, int b){
if (a < 10 || b < 10) // 判定 {
return 0; // 分支一
} else {
return 1; // 分支二
}
}

设计判定覆盖案例时,我们只需要考虑判定结果为true和false两种情况,因此,我们设计如下的案例就能达到判定覆盖率100%:

TestCase1: a = 5, b = 任意数字 //覆盖了分支一

TestCase2: a = 15, b = 15 //覆盖了分支二

设计条件覆盖案例时,我们需要考虑判定中的每个条件表达式结果,为了覆盖率达到100%,我们设计了如下的案例:

TestCase3: a = 5, b = 5 //true, true

TestCase4: a = 15, b = 15 //false, false

通过上面的例子,我们应该很清楚了判定覆盖条件覆盖的区别。需要特别注意的是:

  • 条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。

因此,我们可以这样推论:

  • 完全的条件覆盖并不能保证完全的判定覆盖。比如上面的例子,假如我设计的案例为:
TestCase5: a = 5, b = 15 //true, false 分支一

TestCase6: a = 15, b = 5 //false, true 分支一

我们看到,虽然我们完整的做到了条件覆盖,但是我们却没有做到完整的判定覆盖,我们只覆盖了分支一。

上面的例子也可以看出,这两种覆盖方式看起来似乎都不咋滴。我们接下来看看第四种覆盖方式。

4.1.4 路径覆盖/断言覆盖

路径覆盖又称断言覆盖。它度量了是否函数的每一个分支都被执行了。

所有可能的分支都要执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,因此,测试路径随着分支的数量指数级别增加。

比如下面的测试代码中有两个判定分支:

int foo(int a, int b){
int nReturn = 0;
if (a < 10) {// 分支一
nReturn += 1;
}
if (b < 10) {// 分支二
nReturn += 10;
}
return nReturn;
}

对上面的代码,我们分别针对我们前三种覆盖方式来设计测试案例:

  • 语句覆盖
TestCase a = 5, b = 5 nReturn = 11 //语句覆盖率100%
  • 判定覆盖
TestCase1 a = 5, b = 5 nReturn = 11TestCase2 a = 15, b = 15 nReturn = 0//判定覆盖率100%
  • 条件覆盖
TestCase1 a = 5, b = 15 nReturn = 1TestCase2 a = 15, b = 5 nReturn = 10//条件覆盖率100%

我们看到,上面三种覆盖率结果看起来都达到了100%!但是上面被测代码中,nReturn的结果一共有四种可能的返回值:0,1,10,11,而我们上面的针对每种覆盖率设计的测试案例只覆盖了部分返回值,因此,可以说使用上面任一覆盖方式,虽然覆盖率达到了100%,但是并没有测试完全。

接下来我们来看看针对路径覆盖设计出来的测试案例:

  • 路径覆盖
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. 测试人员不能盲目追求代码覆盖率,而应该设计更全面的测试用例;

4.2 基于IDEA使用Jacoco插件统计【行覆盖率】、分支覆盖率】

Step1 启用 Jacoco 插件



Step2 显示 branch 覆盖率


参考文献

X 参考文献

[软件测试] sonar 常见问题及修复思路【待完善】的更多相关文章

  1. MySQL 5.7基于GTID复制的常见问题和修复步骤(二)

    [问题二] 有一个集群(MySQL5.7.23)切换后复制slave报1236,其实是不小心在slave上执行了事务导致 Got fatal error 1236 from master when r ...

  2. Flink on YARN(下):常见问题与排查思路

    Flink 支持 Standalone 独立部署和 YARN.Kubernetes.Mesos 等集群部署模式,其中 YARN 集群部署模式在国内的应用越来越广泛.Flink 社区将推出 Flink ...

  3. 运维案例 | Exchange2010数据库损坏的紧急修复思路

    ​​关注嘉为科技,获取运维新知 Exchange后端数据库故障,一般都会是比较严重的紧急故障,因为这会直接影响到大面积用户的正常使用,而且涉及到用户数据.一旦遇到这种级别的故障,管理员往往都是在非常紧 ...

  4. redis在实践中的一些常见问题以及优化思路

    1.fork耗时导致高并发请求延时 RDB和AOF的时候,其实会有生成RDB快照,AOF rewrite,耗费磁盘IO的过程,主进程fork子进程 fork的时候,子进程是需要拷贝父进程的空间内存页表 ...

  5. 搞站思路 <陆续完善中>

    只提供思路经验分享.不提供日站方法....一般站点那里最容易出现问题 入手思路: 主站一般都很安全.一般从二级域名下手 多看看那些大站新出来的测试分站点 猜路径别忘了google 考虑看站点下的rob ...

  6. Elasticsearch 常见问题的解决思路

    本文为es性能监控基础的扩展,大家可以先看下性能监控基础,熟悉下es的基本原理.为翻译性质文档,感谢原作者,原始文档地址 类似于汽车的运行方式,Elasticsearch旨在让用户快速上手和运行,而无 ...

  7. MySQL 5.7基于GTID复制的常见问题和修复步骤(一)

    [问题一] 复制slave报错1236,是较为常见的一种报错 Got fatal error 1236 from master when reading data from binary log: ' ...

  8. 从零开始配置Jenkins(二)——常见问题及排错思路

    [前言] 一年多以前就听说Jenkins了,那时知道是它可以完成自动构建,感觉蛮强大的.后来,很多人都说它很恶心.最近,公司需要搭建新的服务器,小编就负责从头开始配置并且发布部署成功每一条线每一个项目 ...

  9. xss漏洞修复,待完善

    1.防止sql注入 /// <summary> /// 分析用户请求是否正常 /// </summary> /// <param name="Str" ...

  10. Sonar常见问题分析

    1.Equality tests should not be made with floating point value 代码举例: if (result == num) //result和num均 ...

随机推荐

  1. MySql Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'

    错误:Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' 原因:同一个ip ...

  2. matlab函数学习笔记

    数值精度 显示精度由format函数控制,不影响原始数据,只控制显示精度 命令 说明 long   short   rat 分数 digits   vpa   pi的输出 命令 显示结果结果 form ...

  3. Verilog 预编译

    Verilog 预编译 Verilog 语言支持宏定义(`define),参数 parameter,局域参数(localparam)以及`include等内容.这些数据常量的支持极大方便数字系统设计. ...

  4. vue移动端购物商场首页制作

    1.搭建项目框架 新建首页主组件及其子组件并将子组件展示出来 2.封装所需接口 3.编写轮播图组件 <template> <div id="swipercom"& ...

  5. python 前言

    # python前言简介: ## 文件的概诉 ```py# 什么是文件 .文件夹? 其实是操作系统暴露给用户可以简单快捷操作硬盘的"接口"``` ## 计算机内部储存数据的原理 ` ...

  6. k8s重启应用

    [root@k8s-master01 opt_k8s]# cat app_list xxx-supervise-srv xxx-recon-srv xxx-mkt-strategy-srv xxx-u ...

  7. Jmeter 接口自动化 对变量【登录密码】进行加密处理

    在我们使用Jmeter测试的过程中,尤其是接口测试,有时候需要对参数进行MD5加密后再进行操作: Jmeter自带的就有MD5加密需要使用的到的jar(注意jmeter版本):commons-code ...

  8. 关于安装Wind金融终端后,启动QT后频繁跳出WDF.dll文件注册成功窗口的问题

    安装Wind金融终端后,启动QT后频繁跳出WDF.dll文件注册成功窗口的信息. 经过排查后发现,主要问题出在系统环境变量上. 安装了wind金融终端后,用户系统环境变量PATH里多了两个wind相关 ...

  9. django生命周期流程以及无有名分组和反向解析 JsonResponse和form表单上传

    django的请求生命周期流程图 要求每个人必须会画,帮助你梳理django的大致流程 路由层 1. 路由匹配:urls.py 这个文件是django框架的总路由文件,意味着还有分路由文件,每个应用都 ...

  10. raid5+lvm随笔

    1.准备磁盘,先做raid,再做lvm; /dev/sdb  /dev/sdc  /dev/sdd  /dev/sde [root@localhost ~]# mdadm -C -v /dev/md5 ...