单元测试应该测试什么?——Right-BICEP
- Right——结果是否正确?
- B (Boundary Conditions)——是否所有的边界条件都是正确的?
- I (Inverse Relationships)——能查一下反响关联吗?
- C (Cross-Check)——能用其它手段交叉检查一下吗?
- E (Force Error)——你是否可以强制错误条件发生?
- P (Performance)——是否满足性能要求?
结果是否正确?
对于测试而言,最首要的任务就是查看程序运行的结果是否与期望的结果一致。
边界条件是否正确?
很多bug都会集中在边界附近,所以应该多注意。
一致性(Conformance)——值是否符合预期的格式?
很多时候,传递给方法的值或者方法运行后产生的值必须符合某种特定的格式。必须考虑的是,如果数据不能像期望的那样符合格式要求,将会出现什么情况。
有序性(Ordering)——一组值是该有序的,还是该无序的?
应该考虑数据顺序,或者是在一个很大的数据集合中某一数据的位置。
区间性(Range)——值是否在一个合理的最大值和最小值的范围之内?
对于一个变量,它所属类型的取值范围可能比需要的或想要的更加宽广。比如,人的年龄、角度等。
引用、耦合性(Reference)——代码是否引用了一些不受代码本身直接控制的外部因素?
如果对于类的状态、其它对象的状态,或者全局应用程序的状态,需要作一些假设,那么就需要对代码进行测试,保证其在假设未满足的情况下运行良好。前置条件和后置条件都必须检测。
- 前置条件(preconditions):系统必须处于什么状态下,该方法才能运行。当前置条件不能满足的情况下程序是否能够正确运行。
- 后置条件(postconditions):方法运行之后将会有哪些状态发生。程序的返回结构必须检查,伴随产生的副作用也必须检查。
存在性(Existence)——值是否存在?(要小心null、0、“”、有时可能还需要注意空格字符串)
存在性的问题很容易出现在方法的参数上,也经常出现在方法要用到的类的属性上。特别是引用类型,要特别注意。当遇到null等值时,采取什么策略需要及早考虑。应该形成一贯的处理策略,形成风格。可以考虑抛出异常,并且Message描述问题应尽量细致明确。
基数性(Cardinality)——是否恰好有足够的值?
也称为集合的势,指集合包含的元素的个数。保证计算得到的数目和你所需要的数目是一致的。
时间性,绝对的或者相当的(Time)——所有事情是否都是按顺序发生的?是否在正确的时间?是否及时?例如:方法调用的时间顺序、代码超时、不同的本地时间、多线程同步等。
测试边界是最有价值的工作,因为bug通常会集中在这里。边界测试需要考虑的主要问题:
- 完全伪造或者不一致的输入数据。
- 格式错误的数据。
- 空值或者不完整的值,如0, 0.0, “”, null之类的。
- 一些与意料中的合理值相去甚远的数值。
- 如果是传入一系列数据,要考虑是否允许重复,考虑值是否应该有特定顺序,顺序是否可能有错误等。
检查反向关联
对于一些方法,我们可以使用反向的逻辑关系来验证它们,如检查一个计算平方根的函数,可以通过对其结果进行平方来检查。要注意的是,当你同时编写了原方法和它的方向测试时,一些bug可能会被在两个函数中都出现的错误所掩盖。在可能的情况下,应该使用不同的原理来编写反向测试。
使用其他手段来实现交叉检查
通过其他经过验证的途径来测试当前被测试方法的结果是否正确。
例如被测试方法存在多个备用算法,这时选择被测试方法没有使用的,并且已经经过验证的算法在测试方法中使用,最后比较测试算法和被测试方法的结果是否一致。
另外也可以通过一些数据从侧面验证被测试方法结果是否正确,例如图书馆中借出的书籍数和在库的书籍数的总和是不变的,这时便可以使用交叉检查,即使用一种数量检查另一种数量。
强制产生错误条件
应当能够通过强制引发错误,来测试你的代码是如何处理所有这些真实世界中的问题的。这些错误可能是:内存耗光、硬盘用满、时钟错误、断网等。
性能特性
即测试在数据量逐渐增加的时候,性能曲线是否能达到预期(稳定)。