单元测试覆盖率

8,958 阅读4分钟

单元测试的目的是,将你的项目划分成小单元,每个单元测试中尽量设计 case 将代码逻辑中的每个分支都运行到。这样所有的单元测试跑下来,项目中的每行代码最好都被跑过一次,即覆盖率尽量去靠近 100%。

很多项目会在 README 上放一个测试覆盖率的 badge,比如: image.png

工具:istanbul(JavaScript 代码覆盖率工具)

代码覆盖率四个维度

测试的时候,我们常常关心,是否所有代码都测试到了。这个指标就叫做“代码覆盖率”(code coverage),它有四个测量维度。

  • 行覆盖率(line coverage):是否每一行都执行了?
  • 函数覆盖率(function coverage):是否每个函数都调用了?
  • 分支覆盖率(branch coverage):是否每个 if 代码块都执行了?
  • 语句覆盖率(statement coverage):是否每个语句都执行了?

行(Lines of Source Code) vs 可执行代码(Lines of Executable Code)

“行覆盖率”中的行是指可执行代码行(Lines of Executable Code),而不是源文件中所有的行(含空行)——(Lines of Source Code)。
可执行代码行:
一般来说,包含语句的每一行都应被视为可执行行。而复合语句(简称为语句块,用{}括起来)会被忽略(但其内容除外)。

function doTheThing ()  // +0 
{                       // +0
    const num = 1;      // +1
    console.log(num);   // +1
}                       // +0

以下内容会被忽略(即视为非可执行行,+0)

1.非语句
一些覆盖率引擎会将以下两点视为可执行行,而 Istanbul 会忽略它们

  • 该行只包含标点符号:}、};、;
  • 定义时的方法(函数)名 2.import、声明
    import、声明都被视为非可执行行(+0),require、赋值等语句视为可执行行(+1)
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

如果某行存在可执行代码,则这一整行会被视为可执行代码行。 而如果一个语句被拆分为多行,则该可执行代码块中,仅第一行会被视为可执行行。

可执行代码行 vs 语句

一般情况下,如果我们遵守良好的代码规范,可执行代码行和语句的表现是一致的。然而当我们将两个语句放一行时,就会得到不同的结果。

// 2 lines、2 statements
const x = 1;console.log(x);
// 1 line、2 statements
const x = 1; console.log(x);

流程控制

JavaScript 的流程控制语句有:

  • if
  • white
  • do … while
  • switch
  • … 运算符:
  • 三目运算符(condition ? exprIfTrue : exprIfFalse) 我们需要确保流程控制的每个边界情况(即分支)都被执行(覆盖)。

测试覆盖率报告

上面代码中注释 //+1 的那些行,在覆盖率报告中,其左侧要么是绿色背景的 Nx,或者是粉红色背景的色块。因此我们可以通过颜色的不同,看出哪些是可执行代码行

image.png

绿色方框内的是 Lines of Source Code 行、红色框内粉红色色块是 Lines of Executable Code 可执行代码行。

其他标识

测试覆盖率报告出现的标识有:

  • 'E':'else path not taken',表示 if/else 语句的 if(含 else if)分支已测试,而 else 分支未测试
  • 'I':'if path not taken',与上面的 'E' 相反,即 if(含 else if)分支未测试
  • 'Nx':表示当前可执行代码行被执行的总次数
  • 粉色(背景色):语句/函数未覆盖
  • 黄色(背景色):分支未覆盖

image.png

覆盖率数据到底有多大意义

  • 覆盖率数据只能代表你测试过哪些代码,不能代表你是否测试好这些代码
  • 不能盲目追求代码覆盖率,而应该想办法设计更多更好的案例,哪怕多设计出来的案例对覆盖率一点影响也没有

参考文章

Vue x Coveralls-辛辛苦苦写完测试也要秀一下
Istanbul 入门教程——阮一峰
看懂【测试覆盖率报告】
How do i read an Istanbul Coverage Report?