测试
基本概念¶
-
验证:发现程序中的问题,从而增加对程序正确性的信心
- 形式化推理(通常称为验证 Verification):验证通过构建形式化证明来证实程序的正确性。手工进行验证是非常繁琐的,而且自动化验证工具的支持仍然是一个活跃的研究领域。尽管如此,程序中的一些小而关键的部分可能会进行形式化验证。
- 代码审查(Code Review):让其他人仔细阅读并非正式地推理你的代码,是发现错误的一种有效方法。这类似于在你写作文时让其他人进行校对。代码审查有助于发现那些可能被编写者忽视的错误或逻辑问题。
- 测试(Testing):在精心选择的输入上运行程序并检查结果。测试是验证过程中最常用的方法之一,通过实际运行程序来检测是否存在错误、性能问题或其他类型的问题。
-
测试优先开发一个功能的流程:
- 确定目标功能的规格(如参数的类型及其它性质)
- 根据目标设计测试
- 完成功能通过测试
- 设计更强的测试,确保正确完成目标功能
设计测试¶
- 测试集的目的:使用尽可能少的资源(测试),覆盖尽可能大的范围
-
把问题划分为一系列子空间,保证每个子空间有一个测试
- 如 max 函数
-
使用划分的边界
- 如对于 max(int, int)->int
- 划分如下
- relationship between a and b_
- a < b
- a = b
- a > b
- value of a
- a = 0
- a < 0
- a > 0
- a = minimum integer
- a = maximum integer
- value of b
- b = 0
- b < 0
- b > 0
- b = minimum integer
- b = maximum integer
- 设计测试
-
全笛卡尔积测试:随所有可能的条件组合测试
-
每个部分覆盖测试:只设计测试案例使得所有条件都出现过
-
黑盒测试
- 只根据对功能的需求(对输入的描述)设计测试案例,而不管功能的具体实现方法
-
白盒测试
- 结合功能的实现设计测试案例
-
测试覆盖率
- 语句覆盖(弱):是否每个语句至少被某个测试用例执行过。
- 工业中通常目标达到 100%
- 分支覆盖(强):分支覆盖考察的是对于程序中的每个
if
或while
等条件语句,其真和假两个方向是否都至少被某个测试用例执行过。- 100% 是理想的目标,在一些对安全要求高的行业
- 路径覆盖(更强):路径覆盖考察的是是否每一种可能的分支组合 — 即程序中的每一条路径 ,都至少被某个测试用例执行过。
- 100% 是不可实现的
- 语句覆盖(弱):是否每个语句至少被某个测试用例执行过。
-
单元测试
- 对于一个良好的程序,每一个模块都应该进行单元测试
- 对单元进行分割,分别进行独立的测试
- 出现错误很容易定位错误的位置
- 一个模块的失败不应该影响到其他的模块
-
集成测试
- 对模块的组合,甚至整个程序进行测试
- 继承测试不能替代单元测试,因为如果只测试单元模块之间的关系,输入是另一个模块的输出,这个输入可能很弱(没有达到规格对前置条件的描述范围),导致模块可能在以后遇到更强的输入时出现错误
-
自动化测试和回归测试
- 自动运行一系列测试案例并得到结果(只是自动运行,测试仍然需要自己测试)
- 修改代码后应该重新运行测试,防止修正 bug 后引入了新的 bug ,这就是回归测试
-
发现 bug 后因该把引起 bug 的输入引入测试集
-
无论是白盒还是黑盒测试都要遵循规格说明
- 测试与规格说明