第五章 如何开发测试用例
5.1 开发测试用例的基本策略
在软件测试中, 有两种不同的测试思路, 它们分别是功能性测试和结构性测试.
功能性测试, 也称为黑盒测试, 其基本理念是: 任何程序都可以被看作是将输入定义域取值映射到输出值域的函数. 采用功能性测试来开发测试用例, 唯一使用的信息是软件的规格说明.
结构性测试, 也称为白盒测试, 即测试人员将根据功能实现的方式来开发测试用例.
功能性测试的优点在于, 其开发的测试用例是与软件实现无关的, 即使实现发生改变, 测试用例依然有效. 功能性测试的缺点在于, 其开发出的测试用例可能存在严重的冗余.
结构性测试的优点在于它可以提供定义良好的测试覆盖率指标, 从而能够可视化地表现软件的已测试范围, 而其缺点在于其开发出的测试用例依赖于实现.
因此, 我们开发测试用例的基本策略是: 采用功能性测试的方法来开发测试用例, 然后利用测试覆盖率指标来提高测试覆盖范围和去除冗余的测试用例, 从而保证测试用例的质量.
上述的测试用例的开发策略体现了这样一种思想:单元测试所测试的是类或对象的行为,而不是类或对象的成员函数;单元测试应该以行为为中心,而不必担心是哪个类在被测试。有些成员函数只是参与到一个特定的功能(feature)中,而不是实现该功能,因此不值得单独测试这样的成员函数。将重点放在测试行为上,而不是每个单独的成员函数上,我们就可以更好地兼顾到测试的覆盖率和重构的简易度。
5.2 边界值测试
边界值测试是基于如下的两个假定:
- 软件的bug更可能出现在输入变量的极值附近.
- 软件失效极少由两个(或更多)缺陷同时发生所引起.
基于以上的假定, 边界值测试按如下的方法产生测试用例:
- 对每个输入变量, 在其最小值、略高于最小值、略低于最大值和最大值处取值, 这4个值被记为min, min+, max-和max.
- 只让一个变量取以上的极值, 而其他变量都取出正常值.
边界值测试很适合于输入变量是互相独立的物理量的情况. 这里的关键词是"独立"和"物理量". 由于这些变量互相独立, 因此我们可以仅让其中一个变量取极值, 而让其他变量取正常值. 由于这些变量是物理量, 因此它们往往存在极值(即使不存在极值, 我们常常也可以人为设定出用于测试的极值).
5.3 健壮性测试
健壮性测试负责测试被测方法对异常情况的处理是否正确. 在单元测试中, 我们应该通过模拟异常情况的发生, 来测试错误处理逻辑.
但是必须注意的是, 如果被测方法本身并没有被要求必须处理某类异常情况, 那么就无需对被测方法进行健壮性测试. 仅对真正需要进行异常情况处理的代码作健壮性测试.
5.4 等价类测试
等价类测试的思想是通过每个等价类中的一个元素来开发测试用例, 从而降低测试用例的冗余度. 等价类测试的关键是确定出类的等价关系, 通常的方法是预测可能的实现, 并考虑在实现中必须提供的功能操作.
等价类测试不仅可以针对输入变量划分等价类, 也可以针对输出结果划分等价类. 因此, 在使用等价类测试时, 要兼顾输入和输出, 通盘考虑, 这样往往会有很好的测试效果. 当划分出合理的等价类之后, 在等价类的边界值处进行边界值测试, 将带来更好的测试效果.
一些常见的划分等价类的标准有:
- 存在与否
- 取值范围
- 元素顺序
- 元素个数
- 数据格式
5.5 决策表测试
下图是一个起示意作用的决策表.
规则1 | 规则2 | 规则3, 4 | 规则5 | 规则6 | 规则7, 8 | |
condition1 | T | T | T | F | F | F |
action1 | o | o o | o | o o | o | o |
关于决策表, 我们有如下说明:
- condition的取值常常是T/F(真/伪), 但也可以是前一节中所讲到的等价类.
- 一种特殊的condition取值是"Don't card"(不关心), 在决策表中以"-"表示.
- 决策表的action有一些特殊的取值, 如"Impossible"(不可能)和"Report error"(报错).
5.6 综合使用多种方法
在前面几节中, 我们已经讲述了若干种测试用例开发技术. 在对被测类和被测方法生成测试用例时, 往往要综合使用这些技术, 而不是单独使用其中的一种或两种.
5.7 测试覆盖率指标
测试覆盖率指标是衡量测试用例质量的指标, 因此当我们开发出测试用例之后, 总是需要使用代码覆盖率监测软件来检验我们的测试用例对被测代码的操练是否已经达到了令人满意的测试覆盖率.
对于C++, 我们推荐使用TestCocoon软件; 对于C#, 我们推荐使用PartCover软件; 对于Java, 我们推荐使用EclEmma.
单元测试用例的开发有尽头吗?一个比较好的指导意见是:一直测试到你的恐惧变成厌恶。正好我们前面所说,单元测试的目的是为我们提供一种“软件夹钳”,或者说一种“安全网”。如果我们已经感觉到单元测试用例已经足够充分,使我们不再担心对代码的修改会引入bug,那么这时就是停止开发单元测试用例的时候了。因此,不必追求100%的测试覆盖率. 这是因为有些代码并不包含任何逻辑, 因此是不需要被测试的. 不要为了测试而测试, 而应该测试那些真正有可能产生bug的代码.