资讯专栏INFORMATION COLUMN

代码测试覆盖率分析

kevin / 2632人阅读

摘要:背景最近我们前端团队在重构大量的组件,为了保证代码质量,我要求团队中的成员必须编写单元测试,并且测试覆盖率达到以上。总结对一个持续集成的项目来说,单元测试非常重要,同时最好具有较高的测试覆盖率。

背景

最近我们前端团队在重构大量的 UI 组件,为了保证代码质量,我要求团队中的成员必须编写单元测试,并且测试覆盖率达到 80% 以上。那么问题来了,为什么是 80% 的覆盖率? 这是一个硬性的考核指标吗?

这里所说的测试覆盖率,是指的是开发人员写的单元测试的覆盖率,不是测试人员的功能测试的覆盖率。

哪些地方需要写单元测试?

为什么需要写单元测试就不再阐述,我相信大家都知道,特别是在持续集成过程中的重要性。但是,从我的经历来看,当前的软件市场环境中,不管是用的瀑布模式,还是螺旋模式,还是敏捷模式,很多软件没有写单元测试。

我也是一个程序员,每天需要写一些的业务代码,对于写单元测试来说,确实需要我很多时间和精力,因为它也需要设计用例和一些体力活。所以在我们的一些项目中也存在很多功能没有单元测试,主要原因有以下几个点:

业务逻辑更新太快,单元测试不可复用;

业务时间紧急,迭代周期时间短,没有时间写单元测试;

UI 上很多测试,通过单元测试代码无法覆盖。

在《软件测试》一书中讲测试的原则,第一条就是:“完全测试程序是不可能的”。所以对于以上部分需不需测试,取决于你软件性质,时间和团队。但是对于满足以下几点代码我建议需要编写单元测试:

和安全相关的代码逻辑;

核心的功能模块,函数;

短期不会发生变化的 UI 组件;

提供外部调用的接口。

测试覆盖率报告

如果完全通过测试覆盖作为质量标准是存在问题的,我们在检查一个测试覆盖了的时候往往会通过一些工具去检查,程序员是可以通过一些方式让数字看上去漂亮,但是这没有意义。我们应该把它作为一种发现未被测试覆盖的代码的手段,同时也是一种学习的手段,为什么这段代码没有覆盖到? 如果这个函数的参数发生了变化会怎么样? 这段代码逻辑怎么这么复杂?

通过分析未被测试覆盖的代码,找到是设计问题,还功能理解有问题,还是说着就是一段废代码,它可以帮助开发者能够更好的理解背后的事情,可以检查程序中的废代码,然后在以后的设计中做很好的抽象,做可测试的代码。

各种开发语言都有对应的测试框架,可以生成测试报告,在本文中我以前端的 javascript 为示例, karma + istanbul 工具生成报告。

karma 是一个测试框架;

istanbul 是 JavaScript 程序的代码覆盖率工具。

怎么生成测试报告这里就不讲,有很多教程,也可以查看官方文档 istanbul。这里我们先来看一下生成出来的测试报告。 以下是 rsuite src/utils 目录下文件的测试报告, 这是打开的一个生成 html 格式的测试报告:

{% asset_img 1.png RSUITE 测试覆盖率 %}

从图中我们可以看到它有四个指标:

Statements: 语句覆盖率,执行到每个语句;

Branches: 分支覆盖率,执行到每个if代码块;

Functions: 函数覆盖率,调用到程式中的每一个函数;

Lines: 行覆盖率, 执行到程序中的每一行。

每一个指标都列出了覆盖的比例和数量情况,其中 StatementsLines 比例和数量是一致的,那它们有什么不同呢?
在代码中往往存在一些书写不规范的情况,比如一行多个语句,这个时候它们统计的覆盖率就会有差异。 这里又有一个值得思考的问题就是,代码覆盖率工具是怎么统计一行多个语句这种代码的? 后面讲到统计原理的时候会讲到。

另外,我们通过图中可以看出 decorate.js 这个文件相对来说测试覆盖率较低,我们进入再具体分析一下,在那些地方没有覆盖到:

{% asset_img 2.png decorate.js 测试覆盖率 %}

从图中我们可以看到红色部分和黄色, 都是在测试用例中没有覆盖到的地方:

getProps 函数,该函数式 export 出去的一个函数,但是在测试用例中没有覆盖到;

typeof size === "object" 代码块没有覆盖到;

Component.propTypes={}.. 这里黄色部分,是一个默认值设置,说明这个默认值一直没有被使用过;

在图中左侧,显示行号的地方有一个 12x9x4x,这个代表了该行语句被执行的次数, 通过这个清晰的报告,我们可以在代码中看出那些函数,那些代码块没有被执行,从而去分析原因,修正测试用例,完善代码逻辑,提高质量。

生成测试报表原理

我先来看一下 istanbul 生成的测试报告中有个 lcov.info 文件, 这里我只贴出关于 decorate.js 文件这部分的内容:

SF:/Users/simonguo/workspace/rsuite/src/utils/decorate.js
FN:25,getClassNames
FN:39,getProps
FN:41,(anonymous_2)
FN:50,decorate
FN:51,(anonymous_4)
FNF:5
FNH:3
FNDA:237,getClassNames
FNDA:0,getProps
FNDA:0,(anonymous_2)
FNDA:12,decorate
FNDA:12,(anonymous_4)
DA:4,1
DA:11,1
DA:18,1
DA:27,237
DA:28,237
DA:30,237
DA:32,237
DA:40,0
DA:41,0
DA:42,0
DA:44,0
DA:51,12
DA:52,12
DA:53,12
DA:54,12
DA:56,12
...

FN 代表函数,
25,39,41,50,51 这些行分布对应源代码中的函数开始的行号,
FNF:5 代表一共有5个函数
FNH:3 其实 3 个函数被测试所覆盖,
FNDA:237,getClassNames 代表了 getClassNames 这个函数被执行了 237 次。
...

等等,在文件中详细记载了行号,以及代码的执行情况,大家可以再对照前面的那张“测试覆盖率”图片进行分析,可以详细的看出整个 lcov.info 文件中记录内容。有了这样一份记录信息就能够生成出一份可视化的测试报告,也可以上传到 coveralls,展示给大家。 那么这里需要思考的问题是,这样一份数据统计记录是怎么统计出来的呢?

如果希望有些代码被忽略,不进入覆盖统计,istanbul 提供注释语法 ,查看Ignoring code for coverage purposes

javascript 覆盖率统计的核心思想,是在源代码相应的位置注入设定的统计代码,当执行测试代码的时候,代码运行到注入的地方,就会执行对应的统计代码,生成覆盖率统计报告。大概步骤如下:

第一步:生成语法树,对源代码进行语法分析,解析,然后生成语法树。

生成出来的结构如下,这段代码来自 esprima, A simple example on Node.js REPL:

> var esprima = require("esprima");
> var program = "const answer = 42";

> esprima.tokenize(program);
[ { type: "Keyword", value: "const" },
  { type: "Identifier", value: "answer" },
  { type: "Punctuator", value: "=" },
  { type: "Numeric", value: "42" } ]

> esprima.parse(program);
{ type: "Program",
  body:
   [ { type: "VariableDeclaration",
       declarations: [Object],
       kind: "const" } ],
  sourceType: "script" }

第二步:注入统计代码,在语法树相应的位置注入统计代码,在程序执行到这个位置的时候对相应的全局变量赋值,确保执行之后能够根据全局变量知道代码的执行流程。到这里就解决了前面说的“一行如果有多个语句怎么统计?”的问题。

第三步:再把注入统计代码的语法树,生成对应的 javascript 代码。

以下是 escodegen 的一段示例代码

// A simple example: the program

escodegen.generate({
    type: "BinaryExpression",
    operator: "+",
    left: { type: "Literal", value: 40 },
    right: { type: "Literal", value: 2 }
});

// produces the string "40 + 2".

第四步:将生成好的 javascript 代码交给执行环境(nodejs或者浏览器)运行。

第五步:执行单元测试,产生的统计信息,放到全局标量中。

第六步:根据全局标量中的覆盖率信息生成特定格式的报告,这样我们就看到了 lcov.info 文件和 .html 文件。

这个步骤是依据 istanbul 统计 javasript 的原理,其他语言的一些统计工具没有接触过,但是基本的思想应该都是大同小异的。在 javasript 对语法分析,生产语法树再还原 javasript 代码是有一些开源工具的,所以如果有兴趣的童鞋要自己实现一套代码覆盖率的功能,只需要写好注入的统计代码逻辑和运行环境的处理。

总结

对一个持续集成的项目来说,单元测试非常重要,同时最好具有较高的测试覆盖率。再次强调测试覆盖率是一种发现未被测试覆盖的代码的手段,它不是一个考核质量的目标。

另外,我们维护的开源项目 rsuite ,是一套 React 的 UI 组件库,如果你对此感兴趣,或者使用中遇到任何问题,可以联系我们 Discord: join chat

本文作者:郭小铭

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84880.html

相关文章

  • 测试基本面试知识

    测试基本知识1、请你分别介绍一下单元测试、集成测试、系统测试、验收测试、回归测试:(1)单元测试:完成最小的软件设计单元(模块)的验证工作,目标是确保模块被正确的编码,使用过程设计描述作为指南,对重要的控制路径进行测试以发现模块内的错误,通常情况下是白盒的,对代码风格和规则、程序设计和结构、业务逻辑等进行静态测试,及早的发现和解决不易显现的错误。(2)集成测试:通过测试发现与模块接口有关的问题。目...

    不知名网友 评论0 收藏0
  • 软件测试考前复习

    摘要:在软件开发过程中,开发小组的人员应该少而精对测试人员在软件开发过程中的任务主要是寻找并修正该错寻找避免软件开发过程中的缺陷,衡量软件的品质,关注用户的需求软件工程这个名词是在年北大西洋公约组织的计算机科学家在美国召开的国际会 在软件开发过程中,开发小组的人员应该少而精 (对)测试人员在软...

    layman 评论0 收藏0
  • 使用Gradle做Java代码质量检查

    摘要:使用做代码质量检查是一个开源的代码质量管理系统,支持超过种编程语言,提供重复代码编码标准单元测试单元测试覆盖率,代码复杂度,潜在注释和软件设计的报告等。 Maven --> Gradle 首先安装gradle:Mac安装 brew install gradle Ubuntu安装 apt install gradle Maven项目切换Gradle项目,再Maven根目录下运行: gr...

    wushuiyong 评论0 收藏0
  • 开发者测试-采用精准测试工具对Spring Boot应用进行测试

    摘要:,整合刚才已经下载好了,现在我们添加文件标签加一行如下然后是和在文件中添加数据源配置连接本地,新建数据库,查询执行项目页面列出来的语句自动建立该项目需要的数据库内容。 简介:本文主要介绍把现今主流的springboot框架项目和精准测试工具进行结合和应用,通过精准测试的数据穿透、数据采集、测试用例与代码的双向追溯、数据分析等一系列精准测试的特有功能,达到对项目质量的保证。  本次环境搭...

    galois 评论0 收藏0
  • Python代码盖率分析工具Coverage

    摘要:简介是一种用于统计代码覆盖率的工具,通过它我们可以检测测试代码的有效性,即测试对被测代码的覆盖率如何。支持分支覆盖率统计,可以生成报告。 Coverage简介 Coverage是一种用于统计Python代码覆盖率的工具,通过它我们可以检测测试代码的有效性,即测试case对被测代码的覆盖率如何。Coverage支持分支覆盖率统计,可以生成HTML/XML报告。XML报告可以集成入Jenk...

    zombieda 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<