软件测试概论
软件质量:测试的终极目标
软件测试的核心目的在于度量和提高软件质量。但「质量」本身是一个多维度、可度量的复杂概念。
软件质量
软件质量(Software Quality)是软件产品满足用户明确或隐含需求的程度的集合。它包含了多个属性或特性,共同决定了软件的优劣。
我们可以从两个主要维度来审视软件质量:
- 静态质量:关注软件产品本身固有的属性,不涉及程序的实际运行。这主要体现在代码和相关文档上。
- 代码层面:结构是否清晰、可维护性如何、可测试性高低。
- 文档层面:需求文档、设计文档是否完整、清晰、无歧义。
- 动态质量:关注软件在实际运行过程中表现出的特性。这是用户能最直观感受到的质量。
- 核心功能:正确性(Correctness)、可靠性(Reliability)、完备性(Completeness)。
- 用户体验:可用性(Usability)、性能(Performance)。
- 长期维护:可维护性(Maintainability)、可信性(Trustworthiness)。
由于软件的复杂性和开发过程的人为因素,完美无瑕的软件几乎不存在,这便引出了软件缺陷的概念。
理解软件缺陷
软件工程在很大程度上是一门与缺陷作斗争的学科。缺陷是普遍存在的,从微不足道的界面瑕疵到可能导致灾难性后果的系统崩溃。
什么是软件缺陷
广义上,任何导致软件不满足用户期望的问题都可以被称为缺陷(Defect),通常也被称为 Bug。
软件缺陷的正式定义
一个软件缺陷是指软件产品中存在的不符合用户需求、产品规格或预期的任何问题。具体表现为:
- 功能缺失:未实现产品说明书中承诺的功能。
- 功能错误:出现了产品说明书中指明不会出现的错误。
- 隐性需求未满足:未达到产品说明书虽未明确指出但应达到的目标(如基本的易用性)。
- 功能冗余:实现了产品说明书范围之外的功能,可能带来安全隐患或兼容性问题。
- 体验不佳:软件难以理解、不易使用,或用户认为其效果不佳。
缺陷的代价:越晚发现,代价越高
软件缺陷的修复成本与其发现阶段密切相关。在软件生命周期的不同阶段发现并修复同一个缺陷,其成本呈几何级数增长。
缺陷修复成本的指数级增长
- 需求阶段发现一个错误,可能只需要修改文档,成本极低。
- 编码阶段发现,需要修改代码并重新编译。
- 测试阶段发现,需要定位问题、修改代码、回归测试,成本显著增加。
- 发布之后由用户发现,不仅修复成本高昂(可能涉及补丁分发、数据迁移),还会损害产品声誉和用户信任。
因此,测试活动应尽早介入,贯穿于整个软件开发生命周期。
软件测试的核心概念
软件测试的定义
关于软件测试,存在多种定义,但其核心思想是一致的。
软件测试
软件测试(Software Testing)是一个为了发现错误而执行程序或系统的过程。它通过人工或自动化的手段,在预设的条件下运行软件,检验其是否满足规定的需求,并弄清预期结果与实际结果之间的差别。
从这个定义中,我们可以提炼出几个关键点:
- 目的:测试的主要目的是发现缺陷,而不是证明软件没有缺陷。
- 过程:测试是一项活动,涉及执行软件。
- 依据:测试需要基于需求和规格说明。
- 方法:通过比较「预期输出」和「实际输出」来判断是否存在问题。
测试的根本局限性
一个广为流传的论断深刻地揭示了测试的本质:
Edsger W. Dijkstra
Program testing can be used to show the presence of bugs, but never to show their absence.
程序测试只能证明缺陷的存在,永远无法证明缺陷不存在。
这意味着,即使我们执行了大量的测试,没有发现任何问题,也不能断言软件是 100% 正确的。我们只能说,在当前的测试范围内,没有发现缺陷。这是因为对一个稍复杂的软件进行穷尽测试(Exhaustive Testing)在时间和成本上都是不现实的。
测试的基本原则
为了在有限的资源下尽可能高效地进行测试,业界总结出了一系列指导原则:
- 尽早并持续测试:将测试活动贯穿于整个软件生命周期,而不是等到编码完成后才开始。
- 测试显示缺陷存在:牢记测试的目的是发现缺陷,成功的测试是发现了未知缺陷的测试。
- 不可能穷尽测试:除了极简单的软件,无法测试所有输入和场景的组合。
- 缺陷集群效应:软件缺陷倾向于聚集在少数模块中,即遵循「二八定律」。
- 杀虫剂悖论:反复使用相同的测试用例,其发现新缺陷的能力会逐渐下降。测试用例需要定期评审和更新。
- 测试活动依赖于背景:不同类型的软件(如电商网站、嵌入式系统、游戏)需要不同的测试策略和方法。
软件测试的分类
软件测试方法繁多,可以从不同维度进行划分,形成一个立体的测试方法体系。
graph TB
subgraph C [按测试阶段]
direction TB
C1(单元测试) --> C2(集成测试) --> C3(系统测试) --> C4(验收测试)
end
subgraph B [按代码可见性]
direction LR
B1(白盒测试)
B2(黑盒测试)
B3(灰盒测试)
end
subgraph A [按是否执行代码]
direction LR
A1(静态测试)
A2(动态测试)
end
静态测试 vs. 动态测试
- 静态测试(Static Testing):不实际运行被测软件,而是通过评审文档(需求、设计)或分析代码(代码审查、静态分析工具)来发现问题。
- 动态测试(Dynamic Testing):实际运行被测软件,输入测试数据,并检查输出结果是否符合预期。我们通常所说的「测试」大多指动态测试。
白盒测试 vs. 黑盒测试
这是最基本、最重要的测试用例设计方法分类。
- 白盒测试(White-box Testing):也称结构测试或玻璃盒测试。测试人员了解程序的内部逻辑结构和代码,基于代码的覆盖率(如语句覆盖、分支覆盖)来设计测试用例。
- 优点:可以发现代码深层的逻辑错误,测试覆盖度高。
- 缺点:无法发现需求规格中遗漏的功能。
- 黑盒测试(Black-box Testing):也称功能测试或数据驱动测试。测试人员不关心程序内部实现,只关心软件的输入和输出,根据需求规格说明书来设计测试用例,验证功能是否正确实现。
- 优点:能从用户视角出发,检查软件是否满足需求。
- 缺点:无法测试到代码中未被使用的「死角」。
测试级别(阶段)
测试活动随着开发过程层层递进,通常分为四个主要级别:
- 单元测试(Unit Testing):针对软件中最小的可测试单元(如一个函数、一个类)进行的测试,通常由开发人员完成。
- 集成测试(Integration Testing):将已经通过单元测试的模块组合起来,测试它们之间的接口和交互是否存在问题。
- 系统测试(System Testing):将整个软件系统作为一个整体进行测试,验证其是否满足所有功能和非功能需求(如性能、安全)。
- 验收测试(Acceptance Testing):在软件交付前,由最终用户或客户代表进行的测试,以确认软件是否满足他们的需求和期望。
测试与软件开发模型
测试策略与软件开发模型紧密相关。
传统瀑布模型与 V 模型
在瀑布模型(Waterfall Model)中,开发过程是线性的,测试被置于编码之后的独立阶段。这种模式的弊端是问题发现晚,修复成本高。
为了改进这一点,V 模型(V-Model)应运而生。它强调了测试与开发各阶段的对应关系,体现了「尽早测试」的思想。
- V 模型的左侧是开发过程。
- V 模型的右侧是测试过程。
- 水平对应关系:每个开发阶段都有一个对应的测试阶段。例如,在进行「需求分析」时,就应该开始准备「验收测试」的计划和用例。
W 模型与敏捷开发
W 模型(W-Model)是对 V 模型的进一步发展,它强调了测试与开发并行。它在 V 模型的基础上,为每个开发阶段都增加了一个并行的测试活动,即「开发 V」和「测试 V」并行。
- 核心思想:开发活动和测试活动同等重要,应尽早同步进行。例如,在评审需求文档时,测试人员就应介入,从可测试性的角度发现需求中的问题。
在敏捷开发模式(如极限编程 XP)中,测试更是被深度集成到每一个迭代周期中,强调持续测试和快速反馈。
PIE 模型:缺陷的传播机制
一个缺陷要被测试发现,需要经历一个过程。PIE 模型描述了这一过程。
PIE 模型
一个软件缺陷从代码中的静态存在到最终被用户观察到,需要满足三个条件:
- 执行(Execution):包含缺陷(Fault)的代码必须被执行到。
- 感染(Infection):缺陷代码的执行必须导致程序产生一个错误的内部状态(Error)。
- 传播(Propagation):错误的内部状态必须传播到程序的最终输出,从而表现为一个可被观察到的外部行为(Failure)。
- Fault:代码中静态的错误,如
for
循环的起始条件写错i=1
而不是i=0
。 - Error:由于 Fault 被执行,程序内存中的某个变量值变得不正确,这是一个错误的中间状态。
- Failure:由于 Error 的传播,程序最终的输出结果与预期不符,这是用户可见的失败。
PIE 模型的启示:
- 执行是前提:如果错误代码永远不会被执行(死代码),它就不会造成问题。
- 感染非必然:即使执行了有 Fault 的代码,也未必会产生 Error。
- 示例:计算数组元素之和,循环错从
i=1
开始。如果测试数组是[0, 5, 3]
,由于array[0]
恰好是 0,即使漏掉了,sum
的中间状态依然是正确的,没有发生「感染」。
- 示例:计算数组元素之和,循环错从
- 传播非必然:即使产生了 Error,也未必会传播为 Failure。
- 示例:计算数组
[3, 5, 4]
的平均值。一个 Fault 导致程序错误地只计算了前两个数的平均值(3+5)/2 = 4
。然而,正确的结果是(3+5+4)/3 = 4
。虽然中间状态(sum 和 count)是错误的(Error),但最终输出的 Failure 被巧合掩盖了。
- 示例:计算数组
思考题
1. 什么是软件质量?
软件质量是软件产品满足用户明确或隐含需求的程度。它是一个多维度的量,可分为静态质量(如代码结构、可维护性)和动态质量(如正确性、可靠性、性能、易用性)。
2. 什么是软件缺陷,它具有哪些危害,如何管理?
软件缺陷:
- 定义:软件中不符合用户需求、产品规格或预期的任何问题。
- 危害:轻则影响用户体验,重则导致系统崩溃、经济损失甚至安全事故。缺陷发现越晚,修复代价呈几何级数增长。
- 管理:包括缺陷预防、发现(测试)、记录报告、分类跟踪、处理和预测等环节。
3. 什么是软件测试?
是为了发现软件错误而执行程序的过程。其目的在于检验软件是否满足规定需求,并找出预期结果与实际结果之间的差异。
4. 测试的普遍观
测试不仅存在于软件领域,更是一种普遍存在于生活、工作等各个领域的活动。它是一种评估和验证的过程,每个人在不同场景下都在扮演测试者的角色。
5. 软件测试的种类
可以从不同维度划分:
- 按代码可见性:白盒测试、黑盒测试、灰盒测试。
- 按是否执行代码:静态测试、动态测试。
- 按测试阶段:单元测试、集成测试、系统测试、验收测试。
6. 软件测试的关键问题是什么?
关键问题包括:测试由谁执行、测试什么、何时测试以及怎样进行测试。核心挑战在于,穷尽测试是不可能的,因此必须设计出最小且高效的测试用例集来尽可能多地发现缺陷。
7. 软件测试的对象是什么?
测试对象不仅是源程序,还包括软件开发全过程产生的所有文档,如需求规格说明书、概要设计说明书、详细设计说明书等。
8. 软件测试的停止准则有哪些?
- 基于时间:达到预定的测试截止日期。
- 基于测试用例:所有测试用例已执行完毕。
- 基于覆盖率:满足了特定的覆盖率标准(如代码覆盖率)。
- 基于缺陷:发现了预定数量的缺陷,或缺陷发现率低于某个阈值。
9. 软件测试的周期性和并行性
- 周期性:指「测试 → 改错 → 回归测试」的循环迭代过程,直至软件质量达到要求。
- 并行性:指测试活动与软件开发活动并行进行,而不是严格的先后顺序。例如,在需求分析阶段就可以开始设计系统测试用例。
10. 软件测试的目的、意义和原则
- 目的:发现软件中尽可能多的缺陷,而不是证明软件是正确的。
- 意义:对用户,是质量的保障;对开发者,是控制风险、提高产品竞争力的关键手段。
- 原则:尽早并持续测试;测试无法穷尽;缺陷具有集群性;杀虫剂悖论等。
11. 软件测试的未来发展趋势
自动化、智能化、工具化、服务化、理论体系化、过程标准化、人员专业化等。
12. 软件测试在软件工程中的位置(在若干开发模型中)
- 大棒开发法/边写边改法:测试是随意的、非结构化的,通常与编码混杂在一起,质量难以保证。
- 瀑布模型:位于编码之后,是一个独立的阶段。
- V 模型:与开发各阶段相对应,测试贯穿于开发全过程的后半部分。
- 敏捷模型:深度集成在每个迭代周期中,强调持续测试和快速反馈。
13. 软件测试的模型
主要有 V 模型、W 模型、X 模型等:
- V 模型:强调了开发各阶段与测试各阶段的对应关系。
- W 模型:强调了测试与开发的并行性,测试伴随整个开发周期。
- X 模型:更适用于探索性、迭代式的开发模式。
14. 软件测试的过程(测试信息流程)
测试过程需要三类输入:软件配置(测试对象)、测试配置(测试计划和用例)和测试工具。
核心流程是:执行测试 → 分析结果 → 发现错误 → 修正错误 → 回归测试。
15. 软件测试的研究
软件测试是一个活跃的学术研究领域,研究方向包括新的测试方法(如蜕变测试、组合测试)、测试自动化技术、测试理论、测试度量等。
16. 软件测试作为一种职业和作为一门科学
- 作为职业:它需要独特的技能(探索、批判性思维)、专业知识和工具,是一项富有创造性和挑战性的工作。
- 作为科学:它研究如何系统性、可度量地评估软件质量,涉及测试策略、用例生成、故障诊断等科学问题,并形成了理论体系。
17. 软件测试的工具
- 测试管理工具:如 TestDirector。
- 自动化功能测试工具:如 QTP, Selenium。
- 性能测试工具:如 LoadRunner。
- 单元测试框架:如 xUnit 系列。
- 白盒测试工具:如静态代码分析工具。
- 测试用例设计:辅助生成组合测试用例,如 PICT。
18. 软件测试的管理
- 时间维:全过程管理(计划、设计、执行、结果管理)。
- 空间维:全方位管理(缺陷、文档、配置、质量管理)。
- 组织维:人员管理(测试人员、小组、机构)。
19. 软件测试的历史
- 1950s(Debugging Oriented):测试与调试(Debugging)不分,主要目的是让程序能跑起来。
- 1960s(Demonstration Oriented):测试的目的是为了「证明」程序是正确的,以演示给客户。
- 1970s(Destruction Oriented):观念发生转变,以 Dijkstra 的思想为代表,认为测试的目的是为了「破坏」程序,即发现错误。
- 1980s(Evaluation Oriented):测试不仅是发现错误,还要对软件质量进行评估。
- 1990s(Prevention Oriented):强调缺陷预防,测试活动前移,尽早介入。
- 2000s 至今(Professional, Automation, AI):测试成为独立的专业领域,自动化和智能化成为主流发展方向。
20. 软件测试的标准
主要有 IEEE、ISO (如 ISO 29119)、CMM 等国际和行业标准,它们对测试过程、文档、技术和管理等方面进行了规范。
21. 解释软件的故障模型 PIE
PIE 模型描述了缺陷被发现的三个必要条件:
- 执行(Execution):包含缺陷(Fault) 的代码必须被执行。
- 感染(Infection):缺陷的执行导致了程序内部状态错误(Error)。
- 传播(Propagation):错误的内部状态传播到最终输出,形成用户可见的故障(Failure)。