7.13 - 代码覆盖率
Key Takeaway
在 7.12 - 代码测试 中我们介绍了如何编写和保存测试。在这节课中,我们将探讨哪些测试对于保证程序质量是至关重要的。
代码覆盖率
术语代码覆盖率用于描述测试执行时可以运行的程序源代码数量。代码覆盖率有许多不同的度量标准。我们将在接下来的部分中介绍一些更有用和更受欢迎的方法。
语句覆盖率
术语语句覆盖率指的是测试例程执行过的代码中语句的百分比。
考虑以下函数:
1 2 3 4 5 6 7 8 9 |
|
调用 foo(1, 0)
可以实现该函数语句的完全覆盖,即函数中的每条语句都会执行。
对于 isLowerVowel()
函数来说:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
这个函数需要两次调用才能实现语句的完全 覆盖,因为语句2和3不可能在一次函数调用中同时执行。
100%的语句覆盖率是很好的指标,但这其实并不足以确保正确性。
分支覆盖率
分支覆盖率是指已经执行的分支的百分比,每个可能的分支分别计算。一个' if语句'有两个分支——一个分支在条件为'真'时执行,另一个分支在条件为'假'时执行(即使没有相应的' else语句'要执行)。一个switch语句可以有很多分支。
1 2 3 4 5 6 7 8 9 |
|
在之前的例子中,调用 foo(1, 0)
可以实现100%的语句覆盖率,并且测试 x > y
的情况,但是分支覆盖率却只有50%。我们需要再调用一次 foo(0, 1)
,这样就可以测到if
语句不执行的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
在 isLowerVowel()
函数中,我们也需要调用两次才能得到100%的分支覆盖率:第一次调用 (例如 isLowerVowel('a')
) 测试前面的几个case分支,第二次调用(例如 isLowerVowel('q')
) 来测试default
分支。能够在一次测试中执行到的分支,不需要分别测试。
考虑下面的函数:
1 2 3 4 5 6 7 8 9 |
|
该函数需要三次调用才能实现100%分支覆盖:compare(1, 0)
测试第一个if
语句为真的分支,compare(0, 1)
测试第一个if
语句为假且第二个if
语句为真的分支。compare(0, 0)
测试两个if
语句都为假,执行else
的分支。执行完这三次测试后,我们就有信心说这个函数被可靠地测试了(三次测试总好过1800亿亿个输入组合吧)。
最佳实践
100%分支覆盖率是我们的测试目标!
循环覆盖率
循环覆盖率(非正式地称为0,1,2测试)指的是,如果你的代码中有一个循环,则应该确保它在迭代0次、1次和2次时正常工作。如果它对2次迭代的情况正确工作,那么它应该对所有大于2次的迭代都正确工作。因此,这三个测试涵盖了所有的可能性(因为循环不能执行负数次)。
考虑下面代码:
1 2 3 4 5 6 7 |
|
为了更好地测试这个函数中的循环,我们需要调用三次该函数:spam(0)
测试0次迭代的情况,spam(1)
测试1次迭代的情况,然后调用 spam(2)
测试 2 次迭代的情况。如果 spam(2)
可以工作,则对于任意 n>2
的情况 spam(n)
都应该可以工作。
最佳实践
使用 “0,1,2测试” 来确保你的循环在不同次数的迭代下都能正常工作。
测试不同类别的输入
在编写接受参数的函数或接受用户输入时,要考虑不同类别的输入会发生什么。在这种情况下,我们使用术语“类别”来表示具有相似特征的一组输入。
例如,对于一个产生整数平方根的函数,用什么值来测试它是有意义的?你可能会从一些正常的值开始,比如“4”。但是用“0”和负数来测试也是一个好主意。
以下是分类测试的一些基本准则:
对于整数,需要考虑函数如何处理负值、零和正值。如果可能的话,还应该检查是否溢出。
对于浮点数,确保考虑了函数如何处理有精度问题的值(略大于或略小于预期值)。适合测试的double
类型值是 0.1
和 -0.1
(用于测试略大于预期的数字)以及 0.6
和 -0.6
(用于测试略小于预期的数字)。
对于字符串,确保你考虑了函数如何处理空字符串(只是一个空结束符)、正常有效的字符串、有空格的字符串以及全是空格的字符串。
如果函数会处理指针,不要忘记测试 nullptr
(如果你不知道这是什么也不要紧,毕竟我们还没介绍过它)。
最佳实践
测试不同类别的输入值,以确保代码单元能够正确地处理它们。