测试是保证程序有效性(Validation)的方法之一,保证有效性的方法包括:
- 正式的证明(formal vertification)
- 他人评测(Code review)
- 测试(Testing)
下面是几种每千行代码的bug
个数的标准:
1-10
:典型的工业标准0.1-1
:高质量,如java
标准库的水平0.01-0.1
:很好、十分安全的水平,NASA
等的标准
测试优先
推荐的编写程序步骤:
- 编写一个函数
- 编写测试函数
- 调试
分区选择测试用例
将类似的输入用例分在一区,在每一分区中选择具有代表性的用例。
例1:BigInteger.mutiply()
/**
* @param val another BigIntger
* @return a BigInteger whose value is (this * val).
*/
public BigInteger multiply(BigInteger val)
虽然这个函数仅接收一个BigInteger
变量,但实际上调用函数的那个BigInteger
也会影响结果,这个函数实际上是双参数的,因此,测试用例的选择中需要考虑2
个,即:
mutiply: BigInteger * BigInteger -> BigInteger
将可能的取值分类:
(2^63, +infinity)
(-infinity, -2^63)
0
、1
、-1
(1,2^63]
[-2^63, -1)
共7
种,因此至少需要49
个测试用例。
例2:max()
/**
* @param a an argument
* @param b another argument
* @return the larger of a and b.
*/
public static int max(int a, int b)
max: int * int -> int
由于是判断a
和b
关系的函数,这里就不要跟上面一样,根据数据的范围划分了,而应该根据数据的关系划分:
a
<b
a
=b
a
>b
在划分中考虑边界情况
bug
经常发生在两个分区的边界数据上,在划分中需要考虑边界情况,如0
、int
的最大值等。
重新考虑上面的max()
函数的测试用例划分,在上面的基础上,为a
和b
的值再进行划分:
0
>0
<0
int
上限和下限
黑盒测试与白盒测试
黑盒:不管功能内部的实现,直接用分区中的数据测试。
白盒:了解了功能是如何实现的之后,根据其实现选择特定数据测试,如函数会根据不同的输入调用不同算法处理,此时,就可以设计会调用不同算法的样例。
记录测试的整个过程
记录整个过程:
- 分区
- 选择测试用例的覆盖方式
- 确定数据
- 记录结果
例子:测试reverseEnd()
覆盖率
覆盖率可以展示出一个测试样例集是否对代码进行了全面的测试,有几种覆盖:
- 表达式覆盖率:测试中运行的表达式在所有表达式中的占比。
- 分支覆盖率:
while
、if
等分支运行的占全部的比例。 - 路径覆盖率:测试中执行的路径在代码所有可能的执行路径中的占比。
表达式覆盖率100%是可以达到的,而后两个比较困难。
单元测试
对程序的每个模块(函数、类)进行单独测试,这些测试就是单元测试,这样可以更加精准的定位错误发生的模块,相反的,对整个程序的测试称为结合测试(不推荐),结合测试中结果可能是对的,但过程是错的,因为错了多次,反而可能导致正确的结果,但此时却发现不了错误,一个单元测试的输入不应该依赖于其他的单元测试。
自动测试
好的测试程序应该是自动的、设计好的,不用人为的进行输入,但也需要人为地设计测试样例。
当发现bug
时,将引发该错误地样例加入测试集。