人工静态方法本质上属于流程上的实践,实际能够发现问题的数量很大程度依赖于个人的能力,所以从技术上来讲这部分内容可以讨论的点并不多。但是,这种方法已经在目前的企业级测试项目中被广泛的应用了,所以还是需要理解这其中的流程,才能更好的参与到人工静态测试中。
自动静态方法,可以通过自动化的手段,以很低的成本发现并报告各种潜在的代码质量问题,目前已经被很多企业和项目广泛采用,并且已经集成到CI/CD流水线了。作为测试工程师,需要完成代码静态扫描环境的搭建。
一、人工静态方法
人工静态方法检查代码错误,主要有代码走查、结对编程、同行评审这三种手段。
A、代码走查(Code Review)
是由开发人员检查自己的代码,尽可能多的发现各类潜在错误。但是,由于个人能力的差异,以及开发人员的思维惯性,很多错误并不能在这个阶段被及时发现。
B、结对编程(Pair Programming)
是一种敏捷软件开发的方法,一般是由两个开发人员结成对子在一台计算机上共同完成开发任务。其中,一个开发人员实现代码,通过被称为驾驶员;另一个开发人员审查输入的每一行代码,通常被称为观察员。
当观察员对代码有任何疑问时,会立即要求驾驶员给出解释。解释过程中,驾驶员会意识到问题所在,进而修正代码设计和实现。
实际执行过程中,这两个开发人员的角色会定期更换。
C、同行评审(Peer Review)
是指把代码递交到代码仓库,或者合并代码分支(Branch)到主干(Master)前,需要和同技术级别或者更高技术级别的一个或多个同事对代码进行评审,只有通过所有评审后,代码才会被真正递交。
如果所在的项目使用GitHub管理代码,并采用GitFlow的分支管理策略,那么在递交代码或者分支合并时,需要先递交Pull Request(PR),只有这个PR经过了所有评审者的审核,才能被合并。这也是同行评审的具体实践。目前,只要采用GitFlow的分支管理策略,基本都会采用这个方式。
对于以上三种方式,使用最普遍的是同行评审。因为同行评审既能较好的保证代码质量,又不需要过多的人工成本投入,而且提交的代码出现问题后责任明确,另外代码的可追溯性也很好。
结对编程的实际效果虽然不错,但是对人员的利用率比较低,通常被用于一些非常关键和底层算法的代码实现。
二、自动静态方法
自动静态方法,主要有以下三个特点:
- 相比于编译器,可以做到对代码更加严格、个性化的检查;
- 不真正检测代码的逻辑功能,只是站在代码本身的视角,基于规则,尽可能多的去发现代码错误;
- 由于静态分析算法并不实际执行代码,完全是基于代码的词法分析、语法分析、控制流分析等技术,由于分析技术的局限性以及代码写法的多样性,所以会存在一定的误报率。
基于这些特点,自动静态方法通常能够以极低的成本发现以下问题:
- 使用未初始化的变量
- 变量在使用前未定义
- 变量声明了但未使用
- 变量类型不匹配
- 部分的内存泄漏问题
- 空指针引用
- 缓冲区溢出
- 数组越界
- 不可达的僵尸代码
- 过高的代码复杂度
- 死循环
- 大量的重复代码块
正是由于自动静态方法具有自动化程度高,检查发现问题的成本低以及能够发现的代码问题广等特点,所以该方法被很多企业和项目广泛应用于前期代码质量控制和代码质量度量。
在实际工程实践中,企业往往会结合自己的编码规范定制规程库,并与本地IDE开发环境和持续集成的流水线进行高度整合。
代码本地开发阶段,IDE环境就可以自动对代码实现自动静态检查;当代码递交到代码仓库后,CI/CD流水线也会自动触发代码静态检查,如果检测到潜在错误,就会自动邮件通知代码提交者。
三、自动静态方法举例
A、自动静态方法检查语法特征错误
左侧的C语言代码,存在数组越界的问题。右侧,是通过C语言的自动静态扫描工具splint发现的这个问题,并给出的分析结果。
B、自动静态方法检查内存空间被释放后继续被赋值的错误
左侧的C语言代码,用malloc函数申请了一个内存空间,并用指针a指向了这个空间,然后新建了一个指针b也指向这个空间,也就是指针a和指针b实际上指向了同一个内存空间。
之后,把指针a指向的空间释放掉了,意味着指针b指向的空间也被释放了。但是,此时代码却试图去对指针b指向的空间赋值,显然这会导致不可预料的后果。
幸运的是,C语言的自动静态扫描工具splint发现了这个问题,并给出了详细解释。
四、Sonar实战
按照 step by step对Sonar进行总结。
- 搭建自己的SonarQube服务器
- 扫描Maven项目,并将结果报告递交到SonarQube服务器
- 在IntelliJ IDE中集成SonarLint插件,在IDE中实现实时的自动静态分析
首先,在Sonar官网下载LTS(Long-term Support)版本的SonarQube 6.7.5。
解压后运行其中的bin/macosx-universal-64目录下的sonar.sh,这里需要注意运行sonar.sh时要带上console参数。
此时,可以尝试访问localhost:9000,并用默认账号(用户名和密码都是admin)登录。
为了简化建立SonarQube的步骤,所有的内容都使用了默认值。
比如,直接使用了SonarQube内建的数据库,端口也采用了默认的9000。但是,在实际工程项目中,为了Sonar数据的长期可维护和升级,通常会使用自己的数据库,需要执行下面这些步骤:
- 安装SonarQube之前,先安装数据库;
- 建立一个空数据库并赋予CRUD权限;
- 修改SonarQube的conf/sonar.properties中的JDBC配置,使其指向新建的数据库;也可以采用同样的方法,来修改默认的端口。
因为要在Maven项目中执行代码静态扫描,为此需要先找到$MAVEN_HOME/conf下的settings.xml文件,在文件中加入Sonar相关的全局配置,具体需要加入的内容如下所示:
<settings>
<pluginGroups>
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.host.url>
http://myserver:9000
</sonar.host.url>
</properties>
</profile>
</profiles>
</settings>
最后,就可以在Maven项目中,执行mvn clean verify sonar:sonar命令完成静态代码扫描。
如果第一次使用这个命令,那么mvn会自动下载依赖maven-sonar-plugin,完成后发起代码的静态扫描,并会自动把扫描结果显示到SonarQube中。
扫描结果是Passd,但同时也发现了三个Code Smell问题,或者说是改进建议。
- Class建议放在package中
- 导入了java.io.BufferedInputStream,但没有在实际代码中使用,建议删除
- 建议变量名字不要包含下划线
至此,已经使用Sonar完成了一次代码的静态扫描。
但是,在日常工作中可能还想要实时看到Sonar分析的结果,这样可以大幅提高修改代码的效率。为此,可以在IDE中引入SonarLint插件。可以通过IDE的plugin管理界面安装SonarLint。
安装完成后重启IDE,就可以在IDE环境中实时看到Sonar的静态分析结果了。
另外,在IDE中绑定SonarQube,就可以把SonarLint和SonarQube集成在一起了。集成完成后,IDE本地的代码扫描就能使用SonarQube端的静态代码规则库了,在企业级的项目中,一般要求所有开发人员都使用统一的静态代码规则库,所以一般都会要求本地IDE的SonarLint与SonarQube集成。
目前,自动静态扫描通常都会和持续集成的流水线做绑定,最常见的应用场景是当提交代码后,持续集成流水线就会自动触发自动静态扫描,这一功能是通过Jenkins以及Jenkins上的SonarQube插件来完成的,在Jenkins中安装了SonarQube Plugin,并且将SonarQube服务器相关的配置信息加入Plugin之后,就可以在Jenkins Job的配置中增加Sonar静态扫描步骤了。