目录
第2讲 软件测试与测试优先的编程
2.1 软件测试
2.1.1 软件测试
测试是提高软件质量的重要手段,它确认软件是否达到可用级别(用户需求),关注系统的某一侧面的质量特性。
测试无法证明程序中不存在错误。测试的目标是发现错误,好的测试应当满足能发现错误、不冗余、最佳特性、不太复杂也不太简单的特性。
程序中的错误量按照残留缺陷率衡量,单位是defects/kloc,代表每千行代码中的错误数量。
测试的等级从低到高分为单元测试、集成测试、系统测试和验收测试,其中要穿插回归测试。
测试可分为静态测试(不执行程序)和动态测试(根据测试用例测试代码的行为)。
测试与调试不同,测试的目标是发现错误,而调试的目标是识别错误的根源并消除错误。
黑盒测试只关注程序外部表现的行为,而白盒测试关注代码的内部结构。在实际开发过程中,bug的出现是不遵循统计规律的,因此对于软件的测试是一个困难的工作。
2.1.2 测试用例和测试优先编程
测试用例是输入、执行条件和期望结果的集合。例如对于函数,有以下的测试用例:
,
测试用例是项目中的宝贵资产。
测试优先的编程可以节省大量的调试时间,流程如下:
- 编写规约;
- 编写符合规约的测试用例,在此过程中理解、修正、完善规约;
- 写代码、执行测试、有问题再改、再执行测试用例,直到通过它。
2.1.3 测试覆盖度
测试覆盖度指已有的测试用例有多大程度覆盖了被测程序,一般用百分比表示。覆盖度可分为函数覆盖、语句覆盖、分支覆盖、条件覆盖、路径覆盖,测试效果和难度的大小:路径覆盖>分支覆盖 >语句覆盖,但路径覆盖基本无法做到,要根据项目具体要求设定测试标准。
2.2 单元测试
单元测试针对软件的最小单元模型开展测试,隔离各个模块,容易定位错误和调试。
在Java中,一般使用JUnit框架进行调试。在JUnit中,将对应类的测试类放在同一个包中,在测试方法前加上@Test,使用以下方法进行断言:
- assertEquals: 断言预期和给定值相等;
- assertTrue, assertFalse, assertNull, assertNotNull: 断言是否是true值等;
- assertSame, assertNotSame: 比较给定的预期和值是否引用相等(使用==);
- assertThat: 使用Hamcrest Matcher进行匹配。
Hamcrest Matcher包括allOf(全部满足),not,containsInAnyOrder等。
2.3 黑盒测试
黑盒测试检查方法是否遵循规约,设计的目标是用尽可能少的测试发现程序中的错误。
2.3.1 测试用例的设计方法
基于等价类划分的测试:针对每个输入数据需要满足的约束条件,划分等价类,每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合,需要满足对称、传递、自反性。
等价类划分法基于的假设:相似的输入,将会展示相似的行为。故可从每个等价类中选一个代表作为测试用例即可,可以降低测试用例的数量。
例如,对于方法BigInteger.multiply(),需要考虑特殊情况,划分等价类如下(a和b相同):
- -1, 0, 1;
- 小正整数,小负整数;
- 大正整数,大负整数;
这样划分出的7个等价类,最终可以设计出49个测试用例。
边界值分析方法(BVA)是对等价类划分方法的补充,它假定大量的错误发生在输入的边界而非中央。在等价类划分时,要将边界作为等价类之一加入考虑,例如程序员经常犯的“差1错误”。
覆盖测试域的两个极值如下:
- 笛卡尔积覆盖(全覆盖):每个组合一个测试用例,但并非每种组合都是可能的,这种方法测试完备,但用例数量多,测试代价高;
- 覆盖每个取值(只覆盖1次):每个维度的每个取值至少被1个测试用例覆盖一次即可,这种方法测试用例少,代价低,但测试覆盖度未必高。
2.3.2 自动化测试和回归测试
自动化测试进行“测试用例的自动执行”,而非“自动生成测试用例”,自动调用被测函数、自动判定测试结果、自动计算覆盖度,降低测试的代价。
回归测试是指一旦程序被修改,重新执行之前的所有测试。发现bug时,要马上写一个可重现该bug的测试用例,并将其加入测试库。
2.3.3 测试策略
测试策略(根据什么来选择测试用例)需要在程序中显式记录下来。其目的是在代码评审过程中,其他人可以理解你的测试,并评判测试是否足够充分。
记录测试策略时,先记录要测试的方法如何进行测试,然后在每个测试函数前注明其覆盖了什么情况。
*2.4 其他测试方法
包括白盒测试,回归测试等,不在考试范围内。