9. 设计二个函数分别计算sinx和cosx_如何计算增量测试覆盖率

7a57bbec9318c58ffa1c50c49f71db86.png

为了保证代码质量,一般会要求提交的源码要有测试用例覆盖,并对测试覆盖率有一定的要求,在实践中不仅会考核存量代码覆盖率(总体覆盖率)还会考核增量代码的覆盖率。

或者说增量覆盖率更有实际意义,测试用例要随源码一并提交,实时保证源码的质量,而不是代码先行,测试用例后补,这有些应付的意思。

对于存量代码覆盖率主流的测试工具(框架)都是默认支持的,配置reporter相关参数,执行完测试用例就会生成测试报告。

对于增量测试覆盖率主流的测试工具一般没有支持,我想计算增量代码貌似不是测试工具该干的事,所以主流测试工具并没有提供这一功能。

那么如果计算增量覆盖率呢?

计算增量测试覆盖率,总共需要3步

  1. 计算出增量代码的所有行号
  2. 计算出测试未覆盖的代码的所有行号
  3. 对比计算增量代码被测试覆盖的比例,得出增量覆盖率

是不是很简单,有没有一种 “道理我都懂,就是过不好这一生的赶脚”

一、计算增量代码的所有行号

代码管理一般都会用到 GIT 这个工具,GIT提供了非常强大的管理增量代码的能力,因此,可以利用GIT这一特性,通过git diff(参考文献1) 这个命令获取增量代码。

git diff命令可以使用如下格式,用来对比不同commit(或分支)间的增量代码

git diff [<options>] <commit> <commit>

其中<commit>可以是分支名,对比分支间的差异,则是 git diff [<options>] targetBranchName sourceBranchName。可以简写为 git diff targetBranchName 表示对比当前分支与目标分支间的代码增量差异。

例如 git diff master 生成当前分支与master分支的增量信息,当有多个文件变化时,会有多个这样的信息块

f22216b5e37e070497223cceee71694c.png
  • 第1部分是发生变化的文件名。---表示文件发生了删除行 +++表示文件发生了新增的行,当---+++后面是文件路径(相对代码根目录的相对路径)。
    • 如果某个文件是新增文件,则---后面是/dev/nul
    • 如果某个文件被删除了,则+++后面是/dev/nul
    • 如果文件发生修改,则---+++后面都有文件名
  • 先介绍第3部分,因为第2部分的解读需要用第3部分辅助。第3部分是详细的含有上下文的增量信息(增量不是指增加,删除也算增量)
    • - 表示这一行被删除
    • + 表示这一行是新增
    • 如果某一行发生修改,则由一条-和一条+表示
  • 第2部分是变化的行号信息,以 @@开头和结尾,中间是删除的行号信息和新增的行号信息,以上图为例
    • -1,11表示,文件出现删除,从第1行开始包含上下文信息一共有11行,在第3部分中分别是第6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 25 行,共11行
    • +1,18表示,文件出现新增,从第1行是包含上线文信息一共18行,在第3部分中分别是第7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25 行,共18行
    • 其中,第8, 9, 10, 12, 13, 14, 15, 16, 25 行是上下文信息,真正删除的行是第6, 17 行,共2行; 新增的行是第7, 11, 18, 19, 20, 21, 22, 23, 24 行,共9行

不难发现,git diff 默认给出的行号信息,不仅包含真正删除和增加的行,还包含一定的上下文信息(为的是给人看时,能看出到底改了哪些行信息,尤其在一个文件有很多相似或重复的语句的情况下)。并且在计算删除的行的行数时(-1,11中的11)要过滤掉增加的行后再计算,反之亦然(+1,18中的18)。

通过上面的命令确实能计算出增量代码的实际行号(有开始行号,有行数,有差异信息),但对于第3部分的差异信息的解析存在一定的难度,不仅要过滤掉对向信息,还要过滤掉上下文信息。

经查阅文档,发现git diff有一个options是--unified=<n>,简写-U<n>。使用此参数来决定diff结果中上下文信息显示n行,而不是默认的3行。

使用 git diff --unified=0 mastergit diff -U0 master看运行结果

7b27ba622e8fb1ff358d36fcae9add39.png

数据结构与不带options的结果基本一致,只不过第2部分和第3部分作为一个整体可能会出现1次或多次,还有一点变化是第2部分行号信息的表达出现了三种格式。

  • -(+)后面只有一个数字,数字是<m>,表示删除(增加)了1行,行号是<m>。此例中-1, +1, +5, -10分别表示第1行删除1行,第1行增加1行,第5行增加1行,第10行删除一行。
  • -(+)后面有两个数字,第一个数字是<m>,第二个数字是0, 表示删除(增加)了0行,即m行没有变化,此例中-4,0表示第4行没有变化
  • -(+)后面有两个数字,第一个数字是<m>,第二个数字是<n>,不是0,表示删除(增加了)n行,起始行号是m。此例中+11,7表示从第11行开始,共增加了7行,行号一次递增,即 11, 12, 13, 14, 15, 16, 17 这几行。

因此,计算增量代码的信息只使用第1部分和第2部分就可以完成,由第1部分计算出增量代码的路径,由第2部分的+后面的数字计算得到增量代码的行号(-后面是删除的行信息,不是增量代码)。本例中a.js文件的增量行号是[1, 5, 11, 12, 13, 14, 15, 16, 17]

由于git diff生成的是固定格式纯文本,解析增量信息时可以按行读取字符串后做正则解析即可。对于linux系统,可以通过管道符|将diff文本导给grep命令(参考文献2),使用正则匹配出需要的信息,命令如下

git diff -U0 master | grep -Po '^+++ ./K.*|^@@ -[0-9]+(,[0-9]+)? +K[0-9]+(,[0-9]+)?(?= @@)'

生成结果如下图,此时,再按行遍历,生成以文件路径为Key,增量行号组成的Array为值的Hash表,用于后续逻辑的索引。

4261a32e82f494880bed7c5c7cbda3bc.png

二、计算测试未覆盖的代码的所有行号

计算未被测试覆盖的行号,需要先在当前分支运行测试脚本生成对应的测试报告。

测试报告有很多种格式,其中http://lcov.info(参考文献3)是一种描述源码覆盖率的纯文本格式的文件,因此它非常便于计算,可利用此文件计算得到未被覆盖的行号。

http://lcov.info文件内容如下图

1b370c7cc3033b93b06ef5db2c5dceb4.png

数据包含以下字段,因工具不同,字段出现的顺序会略有变化

  • TN:<test name> 用例名称,[因工具不同,有的无法生成此字段]
  • SF:<path of the source file> 源文件路径,[因工具不同,有的是绝对路径,有的是相对路径]
  • FN:<line number of function start>,<function name> 函数起始行号,函数名称,[因工具不同,有的函数名无法生成]
  • FNDA:<execution count>,<function name> 函数被执行次数,函数名称,[因工具不同,有的函数名无法生成]
  • FNF:<number of functions found> 识别统计到的函数数量
  • FNH:<number of function hit> 被测试覆盖的函数数量, FNH / FHF即函数覆盖率
  • BRDA:<line number>,<block number>,<branch number>,<taken> 条件分支所在行号,块号,分支号,被执行的次数
  • BRF:<number of branches found> 识别统计到的条件分支数量
  • BRH:<number of branches hit> 被测试覆盖的条件分支数量 BRH / BRF 即分支覆盖率
  • DA:<line number>,<execution count>[,<checksum>] 行号,执行次数, 检验和,[因工具不同,有的有校验和,有的没有]
  • LH:<number of lines with a non-zero execution count> 被测试覆盖的行数量
  • LF:<number of instrumented lines> 可被执行的行数量, LH / LF 即行覆盖率
  • end_of_record 统计信息块结束符,一个文件一个块

由此可见,计算未覆盖代码的行号,只需要提取覆盖率数据中SFDA字段的值即可

  • SF是源码文件路径
  • DA字段有两个数字,第1个是行号,第2个是执行次数,半角逗号分隔,执行次数的值是0的即是未被覆盖的行

同解析diff增量数据一样,解析覆盖率数据时也可以按行读取字符串后做正则解析即可。对于linux系统,可以通过管道符|连接catgrep命令(参考文献2),使用正则匹配出需要的信息,命令如下

cat coverage/lcov.info | grep -Po '^SF:K.*|^DA:K[0-9]+(?=,0)'

生成的结果如下图,得到未被覆盖的行号,再按行遍历,生成以文件路径为Key,增量行号组成的Set为值的Hash表,用于后续逻辑的索引

f2ef00250dd72b2ce6ffc3b139a675c5.png

三、最后一哆嗦

得到上面两份数据,就可以计算得出每个文件的增量覆盖率和总体增量覆盖率了。

但还需要考虑一种情况:由于一些原因(可是配置文件的问题)导致一些源码文件未被统计到测试覆盖率报告中,那么 + 有意为之,则增量文件不用计入增量覆盖率中,此文件的增量覆盖率是 100% + 无意为之,则增加文件需要计入增量覆盖率中,此文件的增量覆盖率是 0

伪代码如下

const incData = {       // 增量代码行号Hash表
    'path/a.js': [1, 2, 3],
    'path/b.js': [2, 3, 4],
    ...
}
const notCovData = {    // 未覆盖代码行号的Hash表
    'path/b.js': 'Set(3) {1, 2, 3}',
    'path/c.js': 'Set(3) {1, 2, 3}',
    ...
}

let notCovLintCount = 0
let lineCount = 0
forEach(incData, (data, file) => {
    const notCovSet = notCovData.get(file)
    const notCovLines = []
    if (notCovSet) {     // 如果增量代码文件中有未覆盖的行数
        forEach(data, lineNum => {
            if (notCovSet.has(lineNum)) {
                notCovLines.push(lineNum)
            }
        })
    } else {    // 增量代码的文件没有被测试覆盖到
        if (!ignore) {  // 如果是无意为之,所有行号均被统计
            notCovLines = notCovLine.concat(data)
        }
    }
    console.log(file, '增量覆盖率:', (1 - notCovLines.length / data.length).toFixed(2) + '%')
    lineCount += data.length
    notCovLineCount += notCovLines.length
})

console.log('总体增量覆盖率:', (1 - notCovLintCount / lineCount).toFixed(2) + '%')

至此,分支间的增量代码的测试覆盖率计算完成。

详细的实现逻辑可参考 nodejs版本

实践与应用

  • 一般会用于CI检测中,在test step后添加增量覆盖率检测脚本,增量覆盖率未达标的代码禁止并入代码库
  • 也可用于git hook中做检测(这会增加提交代码的等待时长,不太建议),增量覆盖率未达标的代码禁止提交,

参考文献

  1. git diff 官方文档
  2. grep man page
  3. lcov geninfo
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值