Open JChehe opened 3 years ago
最近为基于 Egg.js 的项目编写单元测试用例。写得七七八八后,想了解一下单元测试的覆盖率。由于第一次接触测试覆盖率报告,对其中一些细节存在疑惑。
经查阅资料后,整理出这篇文章,希望能解答大家一些关于测试覆盖率报告的疑问。
注:以下内容是基于 Istanbul 覆盖率引擎。不同覆盖率引擎可能会存在一些差异。
通过 Istanbul 得到的测试覆盖率报告
四个测量维度
理解以上四个测量维度并没什么大问题,但还是有些细节可以深究。
“行覆盖率”中的行是指可执行代码行(Lines of Executable Code),而不是源文件中所有的行(含空行)——(Lines of Source Code)。
可执行代码行:
一般来说,包含语句的每一行都应被视为可执行行。而复合语句(简称为语句块,用 {} 括起来)会被忽略(但其内容除外)。
注:对于可执行行的定义,不同覆盖率引擎可能会存在一些差异。
因此:
function doTheThing () // +0 { // +0 const num = 1; // +1 console.log(num); // +1 } // +0
具体以下东西会被忽略(即视为非可执行行,+0):
一些覆盖率引擎会将以下两点视为可执行行,而 Istanbul 会忽略它们:
import { isEqual } from 'lodash'; // +0 const path = require('path'); // +1 require('jquery') // +1 let filePath // +0 const fileName = 'a.txt'; // +1 注:不仅是声明,还有赋值 class Person { // +0 constructor (name) { // +0 this.name = name; // +1 } // +0 static sayHello () { // +0 console.log('hello'); // +1 } // +0 walk () {} // +0 } // +0 function doTheThing () // +0 { // +0 const num = 1; // +1 console.log(num); // +1 } // +0
import、声明都被视为非可执行行(+0),require、赋值等语句视为可执行行(+1)
如果某行存在可执行代码,则这一整行会被视为可执行代码行。
而如果一个语句被拆分为多行,则该可执行代码块中,仅第一行被会视为可执行行。
'use strict'; for // +1 ( // +0 let i=0; // +1 i < 10; // +0 i++ // +0 ) // +0 { // +0 } // +0 console.log({ // +1 a: 1, // +0 b: 2, // +0 }) // +0 function func () { // +0 return { // +1 a: 1, // +0 b: 2, // +0 } // +0 } // +0
另外,不管嵌套语句横跨多少行,可执行行的数目仅会加 1。
foo(1, bar()); // +1 foo(1, // +1 bar()); // +0
细心的读者可能会发现,注释 // +1 的那些行,其左侧都是 Nx 或粉色色块(即这两者与底色——灰色不同)。所以 可以不管以上那些概念,通过颜色的不同(非底色——灰色)即可看出哪些是可执行代码行:
// +1
Nx
绿色方框的是 Lines of Source Code、红色红框内与底色不同的色块是 Lines of Executable Code
关于可执行行的更多信息,可查阅:《sonarqube——Executable Lines》。
一般情况下,如果我们遵守良好的代码规范,可执行代码行和语句的表现是一致的。然而当我们将两个语句放一行时,就会得到不同的结果。
// 2 lines、2 statements const x = 1; console.log(x);
// 1 line、2 statements const x = 1; console.log(x);
左图是 2 lines、2 statements,右图是 1 line、2 statements
JavaScript 的 流程控制语句 有:
运算符:
condition ? exprIfTrue : exprIfFalse
我们需要确保流程控制的每个边界情况(即分支)都被执行(覆盖)。
测试覆盖率报告出现的标识有:
if
else if
else
代码中的某些分支可能很难,甚至无法测试。故 Istanbul 提供 注释语法,使得某些代码不计入覆盖率。
// 忽略一个 else 分支 /* istanbul ignore else */ if (foo.hasOwnProperty('bar')) { // do something }
// 忽略一个 if 分支 /* istanbul ignore if */ if (hardToReproduceError)) { return callback(hardToReproduceError); }
// 忽略默认值 {} var object = parameter || /* istanbul ignore next */ {};
通过注释语法,将 funB 的 if 分支排除。故 Branches 由 2/4 变为 2/3,即总分支数由 4 减为 3。
关于 Istanbul 注释语法的更多信息,请查阅《Ignoring code for coverage purposes》。
最近为基于 Egg.js 的项目编写单元测试用例。写得七七八八后,想了解一下单元测试的覆盖率。由于第一次接触测试覆盖率报告,对其中一些细节存在疑惑。
经查阅资料后,整理出这篇文章,希望能解答大家一些关于测试覆盖率报告的疑问。
通过 Istanbul 得到的测试覆盖率报告
四个测量维度
四个测量维度
理解以上四个测量维度并没什么大问题,但还是有些细节可以深究。
行(Lines of Source Code) vs 可执行代码行(Lines of Executable Code)
“行覆盖率”中的行是指可执行代码行(Lines of Executable Code),而不是源文件中所有的行(含空行)——(Lines of Source Code)。
可执行代码行:
一般来说,包含语句的每一行都应被视为可执行行。而复合语句(简称为语句块,用 {} 括起来)会被忽略(但其内容除外)。
因此:
具体以下东西会被忽略(即视为非可执行行,+0):
非语句
一些覆盖率引擎会将以下两点视为可执行行,而 Istanbul 会忽略它们:
import、声明
import、声明都被视为非可执行行(+0),require、赋值等语句视为可执行行(+1)
如果某行存在可执行代码,则这一整行会被视为可执行代码行。
而如果一个语句被拆分为多行,则该可执行代码块中,仅第一行被会视为可执行行。
因此:
另外,不管嵌套语句横跨多少行,可执行行的数目仅会加 1。
细心的读者可能会发现,注释
// +1
的那些行,其左侧都是Nx
或粉色色块(即这两者与底色——灰色不同)。所以 可以不管以上那些概念,通过颜色的不同(非底色——灰色)即可看出哪些是可执行代码行:绿色方框的是 Lines of Source Code、红色红框内与底色不同的色块是 Lines of Executable Code
关于可执行行的更多信息,可查阅:《sonarqube——Executable Lines》。
可执行代码行 vs 语句
一般情况下,如果我们遵守良好的代码规范,可执行代码行和语句的表现是一致的。然而当我们将两个语句放一行时,就会得到不同的结果。
左图是 2 lines、2 statements,右图是 1 line、2 statements
流程控制
JavaScript 的 流程控制语句 有:
运算符:
condition ? exprIfTrue : exprIfFalse
)我们需要确保流程控制的每个边界情况(即分支)都被执行(覆盖)。
其他标识
测试覆盖率报告出现的标识有:
if
(含else if
)分支已测试,而else
分支未测试。if
(含else if
) 分支未测试。通过注释语法忽略指定代码
代码中的某些分支可能很难,甚至无法测试。故 Istanbul 提供 注释语法,使得某些代码不计入覆盖率。
通过注释语法,将 funB 的 if 分支排除。故 Branches 由 2/4 变为 2/3,即总分支数由 4 减为 3。
关于 Istanbul 注释语法的更多信息,请查阅《Ignoring code for coverage purposes》。
参考资料