第一章 绪 论
本世纪中计算机刚一问世,尽管当时还没有软件这一名称,但软件的重
要组成部分—计算机程序就开始为我们服务了。然而,计算机软件作为一
种 人 类 创 造 的 复 杂 逻 辑 实 体 和 人 们 长 期 以 来 已 经 熟 悉 的 大 多 数 产 品 有 着
许多不 同的 特 点。认 识和掌握这些特点需要大量的实践和研究。只是 70
年代以来形成了软件工程的概念,才使软件产业得以初步建立。
软件测试是保证软件质量的重要手段。本章将对软件生存期、软件测
试的目的和意义、软件测试的发展简史以及应该如何正确对待软件测试的
心理学等问题作一概括的描述。其目的在于,使读者在着 手研究和掌握具
体的测试技术以前,对软件测试建立起正确的、全面的基本概念;同时澄
清在用户甚至在计算机界中仍然流行着的一些错误的和有害的观点。
为了便于读者进一步地研究,本章提供了十分详尽的参考资料目录 。
1.1 软件危机和软件生存期
计算机软件在近 30 多年来经历了曲折的发展道路。在这期间值得一提
的是在 60 年代出现的“软件危机”。
我们知道,随着计算机硬件技术的进步,计算机的元器件质量逐步提
高,整机的容量、运行速度以及工作可靠性有了明显的提高。与此同时,
硬件生产的成本降低了。计算机价格的下跌为它的广泛应用创造了极好 的
条件。在此形势下自然要求软件与之相适应。一方面适应高速度、大容量、
高可靠度的高性能硬件;另一方面要适应在广泛应用情况下出现的大型、
复杂问题对软件技术提出的迫切需求。然而,事实上软件技术的发展未能
满足这些需求。和硬件技术的快速发展相比,软件的确大大地落后了。多
年来由于问题未得到及时解决,致使矛盾日益尖锐。这些矛盾归结起来主
要表现在以下几个方面:
①由于缺乏大型软件开发的经验和软件开发数据的积累,使得开发工
作的计划很难制定。主观盲目地制定计划,执行起来和实际情况有很大差
距,使得经费使用常常突破预算。由于工作量 的估计不够准确,进度计划
难以遵循,开发工作完成的期限一再拖延。延期的项目,为了尽快赶上去,
便要增加人力,结果适得其反,不仅进度未能加快,反而更加延误了。在
这 种 情 况 下 , 软 件 开 发 的 投 资 者 和 用 户 对 开 发 工 作 从 不 满 意 发 展 到 不 信
任。
②作为软件设计依据的软件需求,在开发的初期提得不够明确,或是
未能做出确切的表达。开发工作开始后,软件人员又未能和用户及时交换
意见,使得一些问题得不到及时解决而隐藏起来,造成开发后期矛盾的集
中暴露。这时对多个错综复杂的问题既难于分析,又难于解决。
③开发过程中没有遵循统一的、公认的方法论或是 开发规范,参加工
作的人员之间的配合不够严密,约定不够明确。加之不重视文字资料工作,
1
使得开发文档很不完整。发现了问题,未能从根本上去找原因,只是修修
补补。显然,这样开发出的软件无法维护。
④缺乏严密有效的软件质量检测手段,交付给用户的软件质量差,在
运行中暴露出各种各样的问题。在各个应用领域的不可靠软件,可能带来
不同程度的严重后果。轻者影响系统的正常工作,重者发生事故,甚至酿
成生命财产的重大损失。
这 些 矛 盾 表 现 在 具 体 的 软 件 开 发 项 目 上 。 最 为 突 出 的 实 例 便 是 美 国
IBM 公司在 1963 年至 1966 年开发的 IBM 360 机操作系统。 这一项目在
开发期中每年花费五千万美元,总共投入的工作量为 5 千人—年。参加工
作最多时有 1 千人。总共写出了一百万行源程序。尽管如此多的开销,却
拿不到开发成果。这是一次失败的记录。项目负责人 F.P.Brooks 事后
总结了他在组织开发过程中的沉痛教训,写成《神秘的人 —月》一书。这
个 反 映 软 件 危 机 的 典 型 事 例 成 为 软 件 技 术 发 展 过 程 中 一 个 重 要 的 历 史 性
标志。
从上述软件危机的现象和发生危机的原因分析,要摆脱危机不是一件
简单的事,要从多方面着手解决,把握好软件的特点,抓住它与其它产业
部门工作对象的相同与相异之处加以 对比分析,排除人们的一些传统观念
和某些错误认识是非常重要的。
除去那些规模很小的项目以外,通常开发一个软件要在不同层次的多
个开发人员的配合与协作中完成;开发各阶段之间的工作应当有严密的衔
接关系;开发工作完成以后,软件产品应该面向用户,接受用户的检验。
所有这些活动都要求人们根本改变那种把软件当作个人才智产物的看法,
抛弃那些只按自己的工作习惯,不顾与周围其它人员密切配合的作法。事
实上,这里所列举的情况与研制计算机硬件,甚至与完成一项建筑工程项
目并没有本质的差别。任何参加工程项目的人员,他的才能只能在工程的
总体要求和技术规范的约束下充分发挥和施展。既然我们已经积累了几千
年的工程 学知识, 能不能把 它运用在 软件开发 工作上呢 ?实践表明 ,按工
程化的原则和方法组织软件开发工作是有效的,也是摆脱软件危机的一个
主要出路。
参照工程学的概念,研究软件工程工作的特点进一步改变了原来受到
束缚的传统观念。当我们全面分析软件开发工作的各个 "工序”时,认识到
程序编写只是整个工作的一部分。在它的前后还有更重要的工序。正如同
其它事物一样,从它的发生、发展到达成熟阶段,以至老化和衰亡,有一
个历史发展的过程,任何一个计算机软件有它的生存期 (Life Cycle)。这个
生存期包括 6 个步骤,即:
①计划(Planning)
②需求分析(Requirement Analysis)
③设计(Design)
④程序编写(Coding)
⑤测试(Testing)
⑥运行与维护(Run and Maintenance)
这些步骤的主要任务是:
①计划:确定软件开发的总目标;给出软件的功能、性能、可靠性以
及接口等方面的设想。研究完成该项软件任务的可行性,探讨问题解决的
2
方案;对可供开发使用的资源<如计算机硬、软件、人力等)、成本、可取
得 的 效 益 和 开 发 的 进 度 作 出 估 计 ; 制 定 完 成 开 发 任 务 的 实 施 计 划
(1mplementation Plan)。
②需求分析:对开发的软件进行详细的定义,由软件人员和用户共同
讨论决定,哪些需求是可以满足的,并且给予确切的描述;写出软件需求
说明书(Software Requirement Specifications)或称软件规格说明书,以及初
步的用户手册(System User's Manual),提交管理机构审查。
③软件设计:设计是软件工程的技术核心。在设计阶段应把已确定的
各项需求转换成相应的体系结构,在结构中每一组成部分是功能明确的模
块 。 每 个 模 块 都 能 体 现 相 应 的 需 求 。 这 一 步 称 为 概 要 设 计 (Preliminary
Design)。进而进行详细设计 (Detail Design),即对每个模块要完成的工作
进行具体的描述,以便为程序编写打下基础。上述两步设计工作均应写出
设计说明书,以供后继工作使用并提交审查。
④程序编写:把软件设计转换成计算机可以接受的程序,即写成以某
个程序设计语言表示的源程序清单。这一工作也称为编码。当然,写出的
程序应该是结构良好、清晰易读,且与设计相一致的。
⑤测试:测试是检验开发工作的成果是否符合要求,它是保证软件质
量的重要手段。通常测试工作分为三步,即:
单元测试(Unit Testing)——单独检验各模块的工作。
集成测试(Integrated Testing)——将已测试的模块组装起来进行检验。
确认测试(Validation Testing)——按规定的需求,逐项进行有效性测试,
以决定开发的软件是否合格,能否提交用户使用。
⑥运行和维护: 已交付用户的软件投入正式使用以后便进入运行阶
段。这阶段可能持续若干年,甚至几十年。在运行中可能有多种原因需要
对它进行修改。其原因包括:运行中发现了软件中有错误,需要修正;为
了适应变化了的软件工作环境,需要作相应的变更;为进一步增强软件的
功能,提高它的性能,使它进一步完善和扩充。
以上 6 步表明每个软件从它的酝酿开发,直至使用相当长时间以后,
被新的软件代替而退役的整个历史过程。按此顺序逐步转变的过程可用一
个软件生存期的瀑布模型加以形象化描述。图 1.1 给出了这一瀑布模型。
从图中可看出,从左上到右下如同瀑布流水
3
逐级下落。在最后的运行中可能需要多次维护,图中用环形箭头表示。此外,在
实际的项目开发中,为了确保软件的质量,每一步骤完成以后都要进行复查,如
果发现了问题就要及时解决,以免问题积压到最后造成更大的困难。每一步骤的
复查及修正工作正是图中给出的向上箭头。此外,
图中还指明了 6 个步骤,划分的 3 个阶段:软件定
义阶段、软件开发阶段及软件维护阶段。
值得注意的是,上述软件维护工作不可简单地
看待。原因在于维护工作不仅仅是修改程序。在软
件运行的过程中若有必要修改,得提出充分的修改
理由,经过审核,才能肯定下来。接着需要经历制
订修改计划、确定新的需求、修改软件设计、修改
编码、进行测试以及重新投入运行等一系列步骤。
这些步骤正是上述开发一个新软件的步骤。若是运
行中多次提出修改,将多次经历这些步骤。我们把
这一过程用图 1.2 来表示,并称此为软件生存周期。实际上软件生存,周期和
软件生存期表达的是同一内涵,为简便起见通常只称为软件生存期。
1.2 软件测试的意义
软件测试在软件生存期中占有非常突出的重要位置,究其原因是多方面的。
根据 Boehm 的统计,软件开发总成本中,用在测试上的开销要占 30%到 50%。
如果我们把维护阶段也考虑在内,讨论整个软件生存期,开发测试的成本比例会
有所降低,但不要忘记,维护工作相当于二次开发,乃至多次开发,其中必定也
包含有许多测试工作。因此,有人估计软件工作有 50%的时间和 50%以上的成
本花在测试工作上。
软件测试究竟是什么意思,它包括哪些工作?这些问题常常被一般人误解,
甚至从事计算机工作的人员也可能弄不清楚。
测试(Test)一词最早出于古拉丁字,它有“罐”或“容器”的含义。人们当
时用一种特殊的容器检验金属中含有某种元素是多少。到现在测试一词已经普遍
使用了,在工业。生产和制造业中测试被当作一个常规的生产活动,它常常和产
品的质量检验密切相关。在这些行业中测试的含义似乎是明确的,但在计算机软
件领域内则不然。比如,什么是程序测试,什么是软件测试,它们之间有什么差
别?测试和调试是一回事吗?它们之间又有什么差别?对于这些概念的不同解释可
能会涉及到测试的目的和方法。
“程序测试”的说法最早几乎是和“程序编写”同时出现的。从当时的观点
来看,谁写出的程序,谁去测试它,谁去使用它,测试被当作编写程序以后很自
然要做的一步工作。并且在测试时不仅要发现程序里的错误,而且要排除错误。
这时测试与调试混为一谈,完全不加区分。一段时间里人们谈论和写文章所涉及
到的测试一词,实际上是调试即排错(debug)(请参看本节最后两段)。其实这两者
是完全不同的两个概念。我们还注意到,那时的计算机多用来完成数值计算任务。
程序运行后所得的计算结果常常采用以手工计算其中一部分数据的办法来比较,
4
从而判断程序的正确性。这在当时还是简单易行的。但随着计算机应用领域的拓
广,所写程序要解决的问题也不仅仅限于数值计算了。上述手工计算进行校验的
办法难于适应了,然而对于程序正确性检验的基本模式并没有改变。这就是给出
测试数据,运行被测程序,将所得结果与预期结果进行比较,从而判断程序的正
确性。我们称此为程序正确性测试(参看图 1.3)。
70 年代中以来,形成了软件生存期概念。这时人们对于软件测试的认识更
广泛也更深刻了。这对于软件产品的质量保障以及组织好软件开发工具有着重要
的意义。首先,由于能够把整个开发工作明确地划分成若干个开发步骤,就把复
杂的问题按阶段分别加以解决。使得对于问题的认识与分析、解决的方案与采用
的方法以及如何具体实现在各个阶段都有着明确的目标。其次,把软件开发划分
成阶段,就对中间产品给出了若干个监控点,提高了开发过程的可见度,为各阶
段实现目标的情况提供了检验的依据。各阶段完成的软件文档成为检验软件质量
的主要对象。这时对软件质量的判断决不只限于程序本身。即使只谈程序本身的
正确性,它也和编码以前所完成的需求分析及软件设计工作进行得如何密切相
关。很显然,表现在程序中的错误,并不一定是编码所引起的。很可能是详细设
计、概要设计阶段,甚至是需求分析阶段的问题引起的。因此,即使针对源程
序进行测试,所发现的问题其根源可能在开发前期的各个阶段。解决问题、纠正
错误也必须追溯到前期的工作。正是考虑到这一情况,我们通常把测试阶段的工
作分成若干步骤进行。这些步骤包括:模块测试,集成测试、确认测试和系统测
试。对程序的最小单位——模块进行测试时,检验每个模块能否单独工作,从而
5
发现模块的编码问题和算法问题;进而将多个模块联结起来,进行集成测试,以
检验概要设计中对模块之间接口设计的
问题;确认测试则应以需求规格说明书中
的规定作为检验尺度,发现需求分析的问
题;最后进行的系统测试是将开发的软件
与硬件和其它相关因素(如人员的操作,
数据的获取等)综合起来进行全面的检
验,这样的作法必将涉及到软件的需求以
及软件与系统中其它方面的关系。图 1.4
给出了测试工作与软件开发前期工作的
关系。图中开发工作是自上而下进行的,
而几种不同的测试都会涉及到前期工作
的不同阶段。
如果我们着眼于整个软件生存期,特
别是着眼于编码以前各开发阶段的工作
来保证软件的质量,就需要突破原来对测
试的理解。也就是在开发的过程中,需要
不断地复查与评估、不断地进行检验,以
利于把发现的错误和问题得到及时的解
决,而不让这些错误和问题隐藏起来,影
响后期的开发工作。总之,贯穿在整个开
发各阶段的复查、评估与检测活动,远远
地超出了程序测试的范围,可以统称为确
认、验证与测试活动(V,V&T Validation,
Verificationand Testing)有时为了方便,也
简称为测试,不过这是广义的测试概念。
所谓确认,它是指如何决定最后的软件产品是否正确无误。比如,编写出的
程序相对于软件需求和用户提出的要求是否符合,或者说程序输出的信息是用户
所要的信息吗?这个程序在整个系统的环境中能够正确稳定地运行吗?这里自然
包含了对软件需求满足程度的评价。在软件产品开发完成以后,为了对它在功能、
性能、接口以及限制条件等方面是否满足需求作出切实的评价,需要在开发的初
期,在软件需求规格说明书中明确地规定确认的标准。
所谓验证,它是指如何决定软件开发的每个阶段、每个步骤的产品是否正确
无误,并与其前面的开发阶段和开发步骤的产品相一致。验证工作意味着在软件
开发过程中开展一系列活动,旨在确保软件能够正确无误地实现软件的需求。
确认和验证是有联系的,但也有明显的差别。Boehm 在 1981 年是这样来描
述两者差别的:确认要回答的是:我们正在开发一个正确无误的软件产品吗?而
验证要回答的是:我们正开发的软件产品是正确无误的吗?
总之,确认、验证与测试在整个软件开发过程中作为质量保证的手段,应当
最终保证软件产品的正确性、完全性和一致性。我们可以把确认、验证与测试活
动分为三类(见图 1.5):
6
①完整性检验——验证每一开发阶段(或开发步骤)中产品的完整性;分析每
一产品,确保其内部的一致与完全。例如,分析需求规格说明书,以找出不一致
的需求或矛盾的需求。比如,其中规定输出报告,但该报告所需要的数据是无法
得到的。
②进展检验——保证各个开发阶段(或开发步骤)之间其规格说明书的完全
性和一致性。例如,后一阶段的工作确是前阶段工作的进一步细化。
③适用性与充分性检验——把取得的结果与对问题的理解作比较,保证所完
成的结果是必要而充分的解。
在整个软件生存期各阶段中确认、验证与测试活动包括:
①需求分析阶段
·制定项目的 V,V&T 计划:确定 V,V&T 的目标;安排 V,V&T 的活动;
选择采用的方法和工具;制定进度并作出预算。
·确定与测试用例相关的需求:这些需求构成了一组基础的测试用例。从而
有助于澄清并且确定软件需求的可度量性,同时也作为验收测试的基础。
·复审并分析需求:其目的
在于确保规定的需求能够对整个
问题取得有指望的和可用的结
果,针对问题叙述的清晰性、完
全性、正确性、一致性、可测试
性和可跟踪性进行复审。
②概要设计阶段
·复审并修订 V,V&T 计划。
·针对要执行的逻辑功能而
生成的测试数据,补充软件需求。
·复审并分析概要设计:确
保内部的一致性、完全性、正确
性及清晰性;是否满足需求。
③详细设计阶段
·针对功能测试数据进行设
计:设计要考虑到基于系统物理
结构的测试数据。
·复审并分析详细设计规格
说明:确保内部的一致性、完全
性、正确性及清晰性;
检验详细设计是否对概要设
计做出了正确无误的细化;确认所做的设计满足于需求。
④编码及测试阶段
·完成测试用例规格说明:针对编码过程中对设计的修改补充或修正测试用
例规格说明。
·复审、分析并测试程序:检查是否遵循了编码标准;自动或手工分析程序;
运行测试用例,以保证满足验收要求。
·进行产品验收。
⑤运行及维护阶段
7
·软件评估:对软件的运行情况作出评估,以保证它能继续满足用户的需求。
·软件修改评估:当提出修改
的要求时也要进行评估,修改以后
要进行复审和测试,以确保修改正
确无误。
·回归测试:重新运行前已正
确无误的测试用例,以便消除由于
软件修改而带来的各种错误。
最后,为了较为形象地描述软
件开发面临的实际问题,请读者参
看图 1.6 注。虽然这只是一个比喻,
但的确在一定程度上反映了实际
情况。软件项目的实践一再告诉我
们,为了确保软件产品能够符合用
户的需要,必须着眼于整个软件生
存期,在每个开发阶段采取措施,
进行各个阶段的 V,V&T 活动。
使之不致在开发完成后,发现和用
户的需求有如此大的差距。
在这一节的最后,我们讲一下
关于排错(debug)一词的来源。程序
中排错,我们现在都称为 debug,其原始意义是捉住小虫(bug)。这里有关于该词
的一件轶事,它对理解排错与测试的区别是有益的。
1945 年夏在美国弗吉尼亚某地海军水上武器研究中心运行着 MarkII 计算
机,这是以继电器为元件的老式计算工具。由于没有空调设备,夏夜中的机房很
热。当时正值大战期间,计算任务十分繁忙。可是 MarklI 突然停止了工作,在
多方查找后发现了原因:一只飞蛾从窗外进入,落在继电器的触点上。电磁式继
电器触头将其打扁,致使电路中断而停机。机务人员捉到飞蛾,放于机器运行臼
志,并记载了这一情况。G.M.Hopper 为此创一新词,把排除机器运行的故障
统称为“捉虫”——debugging。此后,人们也用该术语称呼程序排错。其实,
它和测试一词的含义完全不同。
1.3 什么是软件测试
经过了多年的软件开发实践,积累了许多成功的开发经验,同时也总结出不
注:图 1.6 引自伦敦大学《计算中心通讯》,第 53 期(The University COmputer Centre Newsletter,NO.53)。
8