Testing and Test-First Programming(测试和测试优先编程)
一、软件测试
1.1 软件测试是提高软件质量的重要手段
- 软件测试是一个以找bug为目的的过程,并且检验该软件是否达到可用级别(用户需求)。
- 软件测试只关注某一侧面的特性,比如该软件主要执行的功能是什么,我们就要集中精力在这个主要功能上,让它尽可能避免出错。
- 软件测试是提高软件质量的重要手段,但并非唯一、决定性手段,软件测试只能提高正确性。
1.2 即使是最好的测试,也可能出错
用 残留缺陷率(defects/kloc) 来进行评判: 每一千行代码出现的错误个数 。其评判标准如下:
- 1-10 defects/kloc: Typical industry software.(典型行业软件)
- 0.1-1 defects/kloc: High-quality validation. The Java libraries might achieve this level of correctness.
- 0.01-0.1 defects/kloc: The very best, safety-critical validation. NASA and companies like Praxis can achieve this level.
即使是再好的测试,也不能达到百分之一百无错误,也无法证明系统里不存在错误。
1.3 什么是好的测试
- 能发现错误
- 不冗余(不会重复去测试一些已经测试过的数据或者同类的数据)
- 重点关注软件的最佳特性
- 别太复杂也别太简单
1.4 测试的种类
-
单元测试(unit testing) : 通常是指验证测试特定代码部分的功能,在功能层面。对每个类、或者是每个类中的方法进行测试。
单元测试的层面为:function / class
单元测试的工具为:JUnit -
集成测试(integration testing): 针对若干个类或若干个包或若干个组件。两个或多个类、包、组件、子系统的组合执行。
集成测试的层面为:classes / packages / components / subsystems -
系统测试(system testing): 测试完全集成的系统,以验证系统是否满足其要求,从而在最终配置中执行软件。 也指在不同的系统上测试,比如程序员一般是开发端,然后开发好后,能在开发端正常运行,但不一定能在用户端正常运行,此时就要在用户端进行一波测试。系统测试的层面为:system
-
验收测试:在上述三者之后
-
回归测试:
-
静态测试(Static testing): 在没有实际执行程序的情况下执行。Reviews, walkthroughs, or inspections(评审,走查,检查)属于静态测试。静态测试通常是隐式的,而且当编程工具/文本编辑器检查源代码结构或编译器(预编译器)时,检查语法和数据流作为静态程序分析
-
动态测试(Dynamic testing): 描述了动态行为的测试代码,它实际上执行具有给定集合的编程代码的测试用例。动态测试可以在程序100%完成之前开始,以测试特定的特定部分,并应用于离散函数或模块。
1.5 测试与调试
- 测试: 发现是否存在错误
- 调试: 识别错误根源,消除错误。
- 先有测试,发现存在错误了,然后才能去找错误根源并且消除错误。
1.6 黑盒测试与白盒测试
-
白盒测试: 对程序内部代码结构的测试
-
黑盒测试: 关注程序外部功能。对程序外部表现出来的行为的测试
1.7 测试难点所在
- 不可能暴力穷举完所以可能的测试案例
- 单纯靠偶然测试是没有意义的
- 基于样本的统计数据对软件测试意义不大。比如工厂里测试一批产品合不合格,那就抽样检测,样品中不合格率小于百分之一则该批产品合格,但这对软件测试是没有意义的。
- 对于离散的输入,软件行为差异巨大。 大多数正确,少数点出错。
- 无统计分布规律可循
二、测试用例
- 测试用例 = 输入 + 执行条件 + 期望输出
- 好的测试用例
- 最可能发现错误
- 不重复、不冗余
- 最有效
- 既不简单也不复杂
三、测试优先编程(编写代码前编写测试)
3.1 过程
- 先写规范(spec)
- 再写符合spec的测试用例
- 写代码、执行测试、有问题再改、再执行测试用例,直到通过它
3.2 测试驱动开发(TDD)
测试驱动开发(TDD)是一个开发过程,依赖于非常短的开发周期的重复:需求被转化为非常具体的测试用例,然后软件经过改进以通过新的测试。
因此,TDD反对的是允许添加未经证明满足需求的软件的软件开发。即没有被事先证明正确的软件不应该被加入
四、黑盒测试
- 用于检查代码的功能
- 不关心内部实现细节
4.1 黑盒测试发现的错误种类
- 不正确或者缺失的功能
- 接口错误
- 数据类型的错误或者外部数据库中访问的错误
- 行为或性能错误
- 初始化和终止时的错误
4.2 黑盒测试的检测样例
黑盒测试的检测样例应该基于规约、要求和外部描述。但是黑盒测试的用例无法覆盖所有的功能要求,用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误。
4.3 测试用例的等价类划分
4.3.1 测试用例的等价类划分的概念
- 基于等价类划分的测试: 将被测函数的输入域划分为等价类,从等价类中导出测试用例。
- 等价类: 代表着对输入约束加以满足/违反的有效/无效数据的集合。包括无效等价类和有效等价类
4.3.2 为什么用等价类
- 基于的假设:相似的输入,将会展示相似的行为。所以可以从每个等价类中选一个代表作为测试用例即可。从而可以降低测试用例数量。
4.3.3 等价类划分例子
- 需要考虑输入数据的特殊情况:a or b is 0, 1, or -1
- 考虑输入的上限:很大的数是否仍正确?所以我们肯定也应该尝试非常大的整数,大于最大的整数
- 对于其他一些例子,需要考虑边界检测(大量的错误发生在输入域的“边
界”而非中央)
- 测试样例的选择:选择每个方框中的一例作为代表
4.4 测试用例的覆盖
由于输入通常是多维的,每一个维度都具有等价类划分,由此引出两种关于测试用例的选取。两种方法各有优劣。
- 笛卡尔乘积法(全覆盖)
概念: 多个划分维度上的多个取值,要组合起来,每个组合都要有一个用例
例子: 假如有两个输入,第一个输入对应的等价类划分为{1,2,其它值},第二个输入对应的等价类划分为{5,6,其它值},则测试用例至少选3*3种,有(1,5),(1,6),(1,其它值),(2,5),(2,6),…,(其它值,其它值)。
优缺点 : 这种方法的优点是覆盖完备,缺点是测试用例多,代价大。
注意: 虽然看起来是全排列,但是可能存在组合后互相矛盾的情况。 - 部分覆盖
概念 : 每个维度的每个取值至少被1个测试用例覆盖一次即可
例子: 再以上例为例,只要我们选了(1,任意值)的测试用例,则第一个输入的第一个维度就考虑结束了,不必还要继续选(1,5)或(1,6)等等。
优缺点 : 这种方法优点测试用例少,代价小,缺点是覆盖度未必高。
五、白盒测试
- 根据程序执行路径设计测试用例
- 白盒测试一般较早执行
- 独立/基本路径测试:对程序所有执行路径进行等价类划分,找出有代表性的最简单的路径(例如循环只需执行1次),设计测试用例使每一条基本路径被至少覆盖1次
六、测试覆盖
6.1 代码覆盖度
- 概念: 已有的测试用例有多大程度覆盖了被测程序(通常用百分比衡量覆盖度)。
- 代码覆盖度越低,测试越不充分但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高
- 各种覆盖:
- 函数覆盖
- 语句覆盖
- 分支覆盖
- 条件覆盖
- 路径覆盖:覆盖程序中的每条路径(路径数量巨大,难以全部覆盖)
- 测试效果: 路径覆盖>分支覆盖/条件覆盖>语句覆盖>函数覆盖
- 测试难度: 路径覆盖>分支覆盖>语句覆盖
6.2 自动/回归测试
- 自动测试: 自动调用被测函数、自动判定测试结果、自动计算覆盖度
- 回归测试: 一旦程序被修改,重新执行之前所有的测试
6.3 记录测试策略
- 目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分
七、总结
- 学习内容:
- 测试优先编程。在编写代码之前,请先编写测试。
- 为系统地选择测试用例的划分和边界。
- 白盒测试和声明覆盖范围,以填写一个测试套件。
- 单元-尽可能地测试每个模块,进行隔离。
- 自动回归测试,以防止bug返回。
- 防止错误。测试是在代码中查找bug,测试优先编程是在引入它们之后尽早找到它们。
- 认识到了测试的重要性,学会了利用等价类划分来思考和编写覆盖度高的测试用例,并且学习了基于junit框架的自动化单元测试,对编程的规范性有很大提升。