(第四部分:软件实现与测试,第10章)
Created: 2022-06-07 Tue 19:33
第四部分:软件实现与测试
第10章:软件测试方法
软件测试 是为了 发现错误 而 执行程序的过程 。 或者说, 软件测试 是根据软件开发各阶段的 规格说明 和 程序的内部结构 而精心设计一批 测试用例 (即 输入数据 及其 预期的输出结果 ),并利用这些 测试用例 去 运行程序 ,以 发现程序错误 的过程。
Glenford J. Myers 就 软件测试目的 提出以下观点:
根据这样的测试目的, 软件测试的原则 应该是:
软件测试 并不等于 程序测试 。软件测试应贯穿于 软件定义与开发的整个期间 。
因此, 需求分析 、 概要设计 、 详细设计 以及 程序编码 等各阶段所得到的 文档资料 , 包括 需求规格说明 、 概要设计规格说明 、 详细设计规格说明 以及 源程序 ,都应成为 软件测试的对象 。
据美国一家公司的统计表明,在查找出的软件错误中,属于 需求分析 和 软件设计 的错误约占64%, 属于程序编写的错误仅占36%。
到程序的测试为止,软件开发工作已经经历了许多环节,每个环节都可能发生问题。 为了把握各个环节的正确性,人们需要进行各种 确认 和 验证 工作。
测试过程需要三类输入:
Figure 1: 测试信息流
Figure 2: 软件测试与软件开发过程的关系
黑盒测试 方法主要是为了发现:
用黑盒测试发现程序中的错误,必须在 所有 可能的 输入条件 和 输出条件 中确定 测试数据 , 检查程序是否都能产生正确的输出。
现在假设一个 程序 \(P\) 有 输入量 \(X\) 和 \(Y\) 及 输出量 \(Z\) ,在字长为 32位 的计算机上运行。 如果 \(X, Y\) 只取整数,考虑把所有的 \(X, Y\) 值都作为测试数据,按 黑盒方法 进行 穷举测试 。 这样做可能采用的测试数据组为 \((X_i, Y_i)\) , 不同测试数据组合的最大可能数目为 \(2^{32} \times 2^{32} = 2^{64}\) 。
如果程序 \(P\) 测试一组 \(X, Y\) 数据需要1毫秒,而且假定一天工作24小时, 一年工作365天,要完成 \(2^{64}\) 组测试,需要5亿年。
软件的 白盒测试 是对 软件的过程性细节 做细致的检查, 它允许测试人员利用 程序内部的逻辑结构 及 有关信息 , 设计或选择 测试用例 ,对程序 所有逻辑路径 进行测试。 因此白盒测试又称为 结构测试 或 逻辑驱动测试 。
软件人员使用 白盒测试 方法,主要想对程序模块进行检查:
对于一个具有 多重选择 和 循环嵌套 的程序, 独立的路径数目 可能是天文数字。 实行 穷举测试 ,由于 工作量过大 , 需要的时间过长 ,实施起来是 不现实 的。
逻辑覆盖 是以 程序内部的逻辑结构 为基础的设计测试用例的技术,它属于 白盒测试 。
根据 覆盖测试的目标 不同,逻辑覆盖可分为:
Figure 3: 测试用例设计的参考例子
\(L_1(a \rightarrow c \rightarrow e)\)
\(=\{(A > 1) and (B = 0)\} and \{(A = 2) or (\frac{X}{A} > 1)\}\)
\(=(A > 1) and (B = 0) and (A = 2) or (A > 1) and (B = 0) and (\frac{X}{A} > 1)\)
\(=(A = 2) and (B = 0) or (A > 1) and (B = 0) and (\frac{X}{A} > 1)\)
\(L_2(a \rightarrow b \rightarrow d)\)
\(=\{\overline{(A > 1) and (B = 0)}\} and \{\overline{(A = 2) or (X > 1)}\}\)
\(=\{(\overline{A > 1}) or (\overline{B = 0})\} and \{(\overline{A = 2}) and (\overline{X > 1})\}\)
\(=(\overline{A > 1}) and (\overline{A = 2}) and (\overline{X > 1}) or (\overline{B = 0}) and (\overline{A = 2}) and (\overline{X > 1})\)
\(=(A \le 1) and (X \le 1) or (B \neq 0) and (A \neq 2) and (X \le 1)\)
\(L_3(a \rightarrow b \rightarrow e)\)
\(=\{\overline{(A > 1) and (B = 0)}\} and \{(A = 2) or (X > 1)\}\)
\(=\{(\overline{A > 1}) or (\overline{B = 0})\} and \{(A = 2) or (X > 1)\}\)
\(=\{(A \le 1) and [(A = 2) or (X > 1)] or (B \neq 0) and [(A = 2) or (X > 1)]\}\)
\(=(A \le 1) and (X > 1) or (B \neq 0) and (A = 2) or (B \neq 0) and (X > 1)\)
\(L_4(a \rightarrow c \rightarrow d)\)
\(=\{(A > 1) and (B = 0)\} and \{\overline{(A = 2) or (\frac{X}{A} > 1)}\}\)
\(=(A > 1) and (B = 0) and (A \neq 2) and (\frac{X}{A} \le 1)\)
语句覆盖 是设计若干个测试用例,运行被测程序,使得 每一个可执行语句至少执行一次 。
测试用例的设计格式如下:
【输入的 \((A, B, X)\) ,输出的 \((A, B, X)\) 】
满足语句覆盖要求的测试用例是:
参与人数 | 0 |
---|---|
A | 0 |
B | 0 |
C | 0 |
D | 0 |
判定覆盖 是设计若干个测试用例,运行被测程序,使得 程序中每个判断的取真分支和取假分支至少经历一次 。 判定覆盖又称为 分支覆盖 。
测试用例举例1:
测试用例举例2:
条件覆盖 是设计若干个测试用例,运行被测程序,使得 程序中每个判断的每个条件的可能取值至少执行一次 。
测试用例举例1:
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
\([(2, 0, 4), (2, 0, 3)]\) | \(ace (L_1)\) | \(T_1 T_2 T_3 T_4\) | \(c, e\) |
\([(1, 0, 1), (1, 0, 1)]\) | \(abd (L_2)\) | \(\overline{T_1} T_2 \overline{T_3} \overline{T_4}\) | \(b, d\) |
\([(2, 1, 1), (2, 1, 2)]\) | \(abe (L_3)\) | \(T_1 \overline{T_2} T_3 \overline{T_4}\) | \(b, e\) |
测试用例举例2:
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
\([(1, 0, 3), (1, 0, 4)]\) | \(abe (L_3)\) | \(\overline{T_1} T_2 \overline{T_3} T_4\) | \(b, e\) |
\([(2, 1, 1), (2, 1, 2)]\) | \(abe (L_3)\) | \(T_1 \overline{T_2} T_3 \overline{T_4}\) | \(b, e\) |
判定-条件覆盖 是设计足够的测试用例,使得 判断中每个条件的所有可能取值至少执行一次 , 同时 每个判断本身的所有可能判断结果至少执行一次 。
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
\([(2, 0, 4), (2, 0, 3)]\) | \(ace (L_1)\) | \(T_1 T_2 T_3 T_4\) | \(c, e\) |
\([(1, 1, 1), (1, 1, 1)]\) | \(abd (L_2)\) | \(\overline{T_1} \overline{T_2} \overline{T_3} \overline{T_4}\) | \(b, d\) |
条件组合覆盖 是设计足够的测试用例,运行被测程序,使得 每个判断的所有可能的条件取值组合至少执行一次 。
测试用例 | 通过路径 | 覆盖条件 | 覆盖组合号 |
---|---|---|---|
\([(2, 0, 4), (2, 0, 3)]\) | \(ace (L_1)\) | \(T_1 T_2 T_3 T_4\) | \(1, 5\) |
\([(2, 1, 1), (2, 1, 2)]\) | \(abe (L_3)\) | \(T_1 \overline{T_2} T_3 \overline{T_4}\) | \(2, 6\) |
\([(1, 0, 3), (1, 0, 4)]\) | \(abe (L_3)\) | \(\overline{T_1} T_2 \overline{T_3} T_4\) | \(3, 7\) |
\([(1, 1, 1), (1, 1, 1)]\) | \(abd (L_2)\) | \(\overline{T_1} \overline{T_2} \overline{T_3} \overline{T_4}\) | \(4, 8\) |
路径覆盖 是设计足够的测试用例,覆盖 程序中所有可能的路径 。
测试用例 | 通过路径 | 覆盖条件 |
---|---|---|
\([(2, 0, 4), (2, 0, 3)]\) | \(ace (L_1)\) | \(T_1 T_2 T_3 T_4\) |
\([(1, 1, 1), (1, 1, 1)]\) | \(abd (L_2)\) | \(\overline{T_1} \overline{T_2} \overline{T_3} \overline{T_4}\) |
\([(1, 1, 2), (1, 1, 3)]\) | \(abe (L_3)\) | \(\overline{T_1} \overline{T_2} \overline{T_3} T_4\) |
\([(3, 0, 3), (3, 0, 1)]\) | \(acd (L_4)\) | \(T_1 T_2 \overline{T_3} \overline{T_4}\) |
控制流图 是描述程序的 控制流 的一种图示方法。
Figure 4: 控制流图的各种图形符号
Figure 5: 程序流程图与对应的控制流图
当判断中的条件表达式是 复合条件 时,需要改复合条件为 一系列只有单个条件的嵌套的判断 。
Figure 6: 复合逻辑下的控制流图
对于给定的 控制流图 \(G\) ,按 McCabe 给出的 环路复杂性 \(V(G)\) 的计算方法如下:
从程序的 环路复杂性 可导出程序基本路径集合中的 独立路径数 。
独立路径 是指包括一组 以前没有处理 的 语句 或 条件 的一条路径。
- path1: 1-11
- path2: 1-2-3-4-5-10-1-11
- path3: 1-2-3-6-8-9-10-1-11
- path4: 1-2-3-6-7-9-10-1-11
等价类划分 是一种典型的 黑盒测试 方法,也是一种非常实用的重要测试方法。
步骤:
等价类 是指每个 输入域 的 子集合 , 在该 子集合 中, 各个输入数据对于揭露程序中的错误都是等效的 。
把数目极多的 输入数据(有效的和无效的) 划分为若干个 等价类 。 并合理地假定: 测试某等价类的代表值 等价于 对这一类其他值的测试 。即:
因此,可以把 全部可供输入的数据 合理划分为 若干等价类 , 在每一个等价类中取一个数据作为测试的输入, 这样就可以 用少量代表性测试数据,达到测试的要求 。
等价类的划分 有两种不同的情况:
在设计测试用例时,要同时考虑 有效等价类 和 无效等价类 。 软件不能都只接受合理的数据,还要经受意外的考验, 检验出无效的或不合理的数据,这样的软件测试才是全面性的 。
划分等价类的原则 :
在确定了等价类之后,建立 等价类表 ,列出所有划分出的等价类:
输入数据 | 有效等价类 | 无效等价类 |
---|---|---|
…… | …… | …… |
…… | …… | …… |
再从划分出的等价类中按以下 原则 选择测试用例:
- 原则2 是为了 把测试工作量减到最小 ,
- 原则3 则 可把多个错误分开 。
在某程序设计语言的语法中规定:
建立输入等价类表:
输入数据 | 有效等价类 | 无效等价类 |
---|---|---|
标识符个数 | 1个 \(^{(1)}\) ,多个 \(^{(2)}\) | 0个 \(^{(3)}\) |
标识符字符数 | 1~80个 \(^{(4)}\) | 0个 \(^{(5)}\) ,>80个 \(^{(6)}\) |
标识符组成 | 字母 \(^{(7)}\) ,数字 \(^{(8)}\) | 非字母数字字符 \(^{(9)}\) ,保留字 \(^{(10)}\) |
第一个字符 | 字母 \(^{(11)}\) | 非字母 \(^{(12)}\) |
标识符使用 | 先说明后使用 \(^{(13)}\) | 未说明已使用 \(^{(14)}\) |
覆盖所有等价类的测试用例:
① | VAR x, T1234567 : REAL; BEGIN x := 3.414; T1234567 := 2.732; …… |
(1), (2), (4), (7), (8), (11), (13) |
② | VAR : REAL; | (3) |
③ | VAR x, : REAL; | (5) |
④ | VAR T12345…… : REAL; | (6) 多于80个字符 |
⑤ | VAR T$ : CHAR; | (9) |
⑥ | VAR GOTO : INTEGER; | (10) |
⑦ | VAR 2T : REAL; | (12) |
⑧ | VAR PAR : REAL; BEGIN…… PAP := SIN(3.14 * 0.8) / 6; |
(14) |
边界值分析 也是一种 黑盒测试 方法,是对 等价类划分 方法的补充。
人们从长期的测试工作经验中得知, 大量的 错误 是发生在 输入 或 输出 范围的 边界 上, 而不是在输入范围的内部。
使用 边界值分析 方法设计 测试用例 ,首先应分析 边界 情况。 通常 输入等价类 与 输出等价类 的 边界 是需要认真考虑的。 应当选取
作为 测试数据 ,而不是选取等价类中的典型值或任意值作为测试数据。
边界值分析方法选择测试用例的原则在很多方面与等价类划分方法类似。
程序的输入文件由一些包含80个字符的记录(卡片)组成。 输入数据记录格式如图所示:
Figure 7: 学生考卷评分和成绩统计程序输入数据形式(试卷部分)
Figure 8: 学生考卷评分和成绩统计程序输入数据形式(学生答卷部分)
记录可分为3组:
程序的输出有4个报告:
输入数据 | 测试用例 |
---|---|
输入文件 | 【空输入文件】 |
标题 | 【没有标题记录】【标题只有一个字符】【标题有80个字符】 |
试题数 | 【试题数为1】【试题数为50】【试题数为51】【试题数为100】【试题数为999】【试题数为0】【试题数含有非数字字符】 |
标准答案记录 | 【没有标准答案记录,有标题】【标准答案记录多一个】【标准答案记录少一个】 |
学生人数 | 【0个学生】【1个学生】【200个学生】【201个学生】 |
学生答题 | 【某学生只有一个回答记录,但有两个标准答案记录】【该学生是文件中的第一个学生】【该学生是文件中的最后一个学生(记录数出错的学生)】 |
【某学生有两个回答记录,但只有一个标准答案记录】【该学生是文件中的第一个学生(指记录数出错的学生)】【该学生是文件中的最后一个学生】 |
输出数据 | 测试用例 |
---|---|
学生成绩 | 【所有学生的成绩都相等】【每个学生的成绩都互不相同】【部分(不是全体)学生的成绩相同(检查是否能按成绩正确排名次)】【有个学生得0分】【有个学生得100分】 |
输出报告 \(^{(1)}\) \(^{(2)}\) | 【有个学生的学号最小(检查按学号排序是否正确)】【有个学生的学号最大(检查按学号排序是否正确)】【适当的学生人数,使产生的报告刚好印满一页(检查打印页数)】【学生人数使报告印满一页尚多出1人(检查打印换页)】 |
输出报告 \(^{(3)}\) | 【平均成绩为100分(所有学生都得满分)】【平均成绩为0分(所有学生都得0分)】【标准偏差为最大值(有一半学生得0分,其他100分)】【标准偏差为0(所有学生得成绩都相等)】 |
输出报告 \(^{(4)}\) | 【所有学生都答对了第一题】【所有学生都答错了第一题】【所有学生都答对了最后一题】【所有学生都答错了最后一题】【选择适当的试题数,使第四个报告刚好印满一页】【试题数使报告印满一页后,刚好剩下一题未打】 |
通常 软件测试过程 按4个步骤进行,即
Figure 9: 软件测试的过程
单元测试(unit testing) 又称为 模块测试 , 是针对软件设计的最小单位 程序模块 进行 正确性检验 的测试工作。 其 目的 是在于 发现各模块内部可能存在的各种差错 。
单元测试需要从程序的 内部结构 出发设计测试用例。 多个模块可以平行地独立进行单元测试 。
组装测试(integrated testing) 也叫做 集成测试 或 联合测试 。 通常,在单元测试的基础上,需要将所有模块按照设计要求组装成为系统。
需要考虑的问题是:
确认测试(validation testing) 又称 有效性测试 。 它的任务是 验证软件的有效性 , 即 验证软件的功能和性能及其他特性是否与用户的要求一致 。
系统测试(system testing) 是将通过 确认测试 的软件,作为整个 计算机系统的一个元素 , 与 计算机硬件 、 外设 、 某些支持软件 、 数据 和 人员 等其他 系统元素 结合在一起, 在 实际运行(使用) 环境下,对计算机系统进行 一系列的组装测试和确认测试 。
系统测试的目的 在于通过与系统的需求定义做比较,发现 软件与系统定义不符合 或 与之矛盾 的地方。
系统测试的测试用例 应根据 系统的需求规格说明书 设计,并在 实际使用 环境下运行。
Figure 10: 各测试步骤中的测试种类
人工测试 不要求在计算机上实际执行被测程序, 而是以一些 人工的模拟技术 和一些 类似动态分析所使用的方法 对程序进行 分析和测试 。
静态分析 是要对源程序进行 静态检验 。通常采用以下方法进行:
静态分析 中进行 人工测试的主要方法 有 桌面检查 、 代码评审 和 走查 。
经验表明,使用这种方法能够有效地发现30%~70%的 逻辑设计和编码错误 。
自动化测试 就是使用 自动化测试工具 或 其他手段 , 按照 测试工程师的预定计划 对软件进行 自动测试 ,
自动化测试 不能 完全取代 手工测试 ,两者 互为补充 。 据统计, 自动化测试 能够找出约 30% 的缺陷, 大多数缺陷仍然需要依靠手工测试来发现 。
测试脚本 是一组测试工具执行的 指令集合 , 既可以通过 录制测试的操作步骤 而产生,也可以直接 用脚本语言编写 。
根据 使用的脚本类型的不同 ,可将 自动化测试框架 分为:
还可以从 应用角度 对 自动化测试框架 进行分类,例如:
调试(debug) 也称 排错 或 纠错 ,它是紧跟在测试之后要做的工作。 但与测试不同之处在于: