Chapter 22 Developer Testing
开发者测试
测试是最常见的改善质量的活动——这种实践得到许多业界和学界研究,以及商业经验的支持。
·单元测试(Unit testing)是将一个程序员或者开发团队所编写的,一个完整的类,子程序或者小程序
,从完整的的系统中隔离出来进行测试。
·组件测试(Component testing)是将一个类,包,小程序或者其他程序元素,从一个更加完整的系
统中隔离出来进行测试,这些测试代码涉及到多个程序员或者多个团队。
·集成测试(Integration testing)是对两个或者更多的类,包,组件或者子系统进行联合测试,这些
组件由多个程序员或者开发团队所创建。这种测试通常有了2个可以进行测试的类的时候就应该尽快开始
,并且一直持续到整个系统的开发完成。
·回归测试(Regression Testing)是指重复执行以前的测试用例,以便在原先通过了相同的测试集合
的软件中查找缺陷。
·系统测试(System testing)是在最终的配置下(包括同其他软硬件系统的集成)运行整个软件。以
便测试安全,性能,资源消耗,时序方面的问题,以及其他无法在低级集成上测试的问题。
开发者进行的测试,通常包括单元测试,组件测试和集成测试,但有时候还会包括回归测试和系统测试
。
测试通常分为2大类:黑盒测试和白盒测试(或者玻璃盒测试)。
有些程序员会将术语“测试testing”和“调试debugging”混用,但是严谨的程序员会区分这两种活动。测
试是一种检查错误的方法,而调试意味着错误已经被发现了,要做的是诊断错误消灭造成这些错误的根
本原因。
22.1 Role of Developer Testing in Software Quality
开发者测试在软件质量中的角色
测试对于绝大多数开发人员来说都是一种煎熬。
你必须期望在你的代码里由错误。尽管这种期望似乎有悖常理,但是你应该期望找到这个错误的人是你
,而不是别人。
怎样利用开发者测试的结果?最直接地,你可以用这个结果来评估正在开发的产品的可靠性。
测试结果的另一个用途是,他可以用于指导对软件的修正,并且通常也是这样。
最后,测试发现缺陷的记录有助于你归纳出程序中最常见错误的类型。你可以用这个信息去选择适当的
培训课程,指引今后的技术复查活动,设计未来的测试用例。
·Testing During Contruction 构建中测试
构建期间,通常你会写一个子程序或者类,现在头脑中检查它,然后对它进行复查或者测试。
无论你的集成测试或者系统测试策略如何,在将一个部分同其他部分组合之前,你都需要对它进行彻底
的单元测试。
如果讲几个没有经过测试的子程序放到一起,结构发现了一个错误,那么这几个子程序都有嫌疑。假如
每次只将一个子程序加入到此前经过测试的子程序集合中,那么一旦发现了错误,你就会知道这是新子
程序或者其接口所引发的问题,调试就会轻松很多。
22.2 Recommended Approach to Developer Testing 开发者测试的推荐方法
采用系统化的开发者测试方法,能最大限度提高你发现各种错误的能力,同时让你的花费也最少。
·对每一项相关的需求进行测试,以确保需求都已经被实现。
在需求阶段就计划好这一部分的测试用例,或者至少尽早开始——最好在你开始编写待测试
的单元之前。
安全级别,数据存储,安装过程以及系统可靠性等,常常在需求阶段被忽略。
·对每一个相关的设计关注点进行测试。
·用基础测试(basis testing)来扩充针对需求和设计的详细测试用例。
·使用一个检查表(check list),其中记录着你在本项目迄今所犯的,以及在过去的项目所犯的错误类
型。
·Test First or Test Last 测试先行还是测试后行
1. 在开始写代码之前先写测试用例,并不比之后写要多花工夫。只是调整了编写活动的顺序而已。
2. 假如你先编写测试用例,那么你将可以更早发现缺陷,同时也更容易修正它们。
3. 先编写测试用例,将迫使你在开始写代码之前至少思考一下需求和设计。
4. 先写,能更早地把需求上的问题暴露出来。因为对一个糟糕的需求来说,要写出测试用例是一件困难
的事情。
5. 如果你保存了最初编写的测试用例——这是你应该做的, 那么先进行测试并非唯一的选择,你仍然可
以最后在进行测试。
总而言之,测试先行的编程是过去十年中所形成的最有用的软件开发实践之一。同时也是一
个非常好的通用方法。
·Limitations of Developer Testing 开发者测试的局限性
1. 开发者测试倾向于“干净测试”。
开发人员往往做一些检验代码能否工作的测试(干净测试,clean tests),而不是做所有可
能让代码失效的测试(肮脏测试,dirty tests)。
在不成熟的测试机构里,肮脏测试同干净测试的数量比是1:5。成熟的测试机构倾向于让肮
脏测试的数量是干净测试的5倍。
2. 开发者测试对覆盖率有过于乐观的估计。
3. 开发者测试往往会忽略一些更复杂的测试覆盖率类型。
大多数开发人员看到的测试覆盖率应该称作“100%的语句覆盖率”。这只是一个好的开始,但
这还远远不够。更好的覆盖率标准是所谓的“100%分支覆盖率”,也就是对每一个判断语句至少测试一个
真值和一个假值。
22.3 Bag of Testing Tricks 测试技巧锦囊
1. Incomplete Testing 不完整测试
由于进行完全测试实际上是不可能的,因此测试的窍门就在于选择那些最有可能找到错误的
测试用例。当你规划测试的时候,要去除那些不会告诉你任何新情况的测试用例。
2. Structure Basis Testing 结构化的基础测试
所需基础测试用例的最少数量可以用以下简单方法计算:
a. 对通过子程序的直路,开始的时候记1.
b. 遇到下面的每个关键字或者其等价物时,加1.
if, while, repeat, for, and 以及 or。
c. 遇到每一个case语句就加1,如果case语句没有缺省的情况,则再加1.
这样的出的数字,意思是至少需要6个测试用例。
2. Data-Flow Testing 数据流测试
数据流测试基于如下观念:数据使用的出错几率至少不亚于控制流。
Boris Beizer生称,全部代码中至少有一半是数据声明和初始化。(1990)
数据的状态可以是以下3中状态之一:
a 已定义(defined)
b 已使用(used)
c 已销毁 (killed)
除了这3个之外,为了方便起见,还有一些术语来描述对变量进行某种操作之前或之后,控制流进入或退
出某个子程序的状态。
d 已进入(entered)
e 已退出(exited)
当工作毫无进展的时候,就应该列出所有的已定义-已使用的组合。这样做看起来工作量很大,但他保证
你能够获得用基础测试方法并不能轻松发现的用例。
·Equivalent Partitioning 等价类划分
一个好的测试用例应该覆盖可输入数据中的很大一部分。如果两个用例能揭示错误完全相同,那么只
要一个就够了。“等价类划分”的概念是这一想法的严格表达形式,应用它有助于减少所需用例的数量。
·Error Guessing 猜测错误
“猜测错误”这一措辞变现了对这一睿智概念的浅薄理解。它的真正含义应该是在猜测程序会在哪里出错的
基础上建立测试用例,尽管这也意味着猜测中会有一些牵强附会的成分。
如果你保留了一份记录过去所犯错误种类的列表,那么你就能提高“猜测错误”的命中率。
·Boundary Analysis 边界值分析
运用边界值条件进行测试最丰硕的战果之一就是off-by-one错误。这种错误即当你想用num时,写成了
num-1;当你想用“>”的时候写成了“>=”,这些都是常见的失误。
边界值分析的思想就是写一些测试用例来测试边界值条件。
·Compound Boundaries 复合边界值
有一种边界值更加隐蔽,就是当边界条件涉及到互相关联的多个变量的时候。例如,2个变量相乘,他们
的值都是大的正数,负数,零。 如果传入的字符串都长得很不寻
常呢?
·Use Test Cases That Make Hand-Checks Convenient 采用容易手工检查的测试用例
22.4 Typical Errors 经典错误
·Which Classes Contain the Most Errors 哪些类包含最多的错误
绝大多数错误往往与少数几个具有严重缺陷的子程序有关。下面是错误和代码之间的普遍关系。
1. 80%的错误存在于项目20%的类或者子程序中。
2. 50%的错误被发现于项目5%的类当中。
如果你认为这些关系无关紧要,很可能是因为你对下面几个结论一无所知。
首先,项目中20%的子程序占用了80%的开发成本。
其次,无论高缺陷率子程序在成本中所占的具体比例如何,这些子程序的成本的确是异常高昂的。
再次,子程序开发成本昂贵带来的影响也显而易见。随话说,“时间就是金钱”,推论是“金钱就是时间”,
也就是说,如果能避免卷入那些烦人的子程序中,那么你就可以省下近80%的成本,从而节约一大段开
发时间。这清晰的描述了软件质量的普遍原则:提高质量就能缩短开发周期,同时降低开发成本。
最后,避免维护惹人厌烦的子程序同样具有明显的重要意义。维护工作应该围绕如何确定容易出问题的
子程序,如何把这些部分推到重来,重新设计并编写代码。
·Errors By Classification 错误的分类
Boris Beizer将多个研究的数据综合起来,得到一种非常详尽的错误分类方法。下面是它的研究结果总
结:
25.18% 结构方面的问题
22.44% 数据
16.19% 已实现的功能
9.88% 构建
8.98% 集成
8.12% 功能需求
2.76% 测试的定义或者执行
1.74% 整个系统,软件架构
4.71% 未归类
下面是数据给我们的启示,提示:
1. 大多数错误的影响范围是相当有限的。
85%的错误可以在修改不超过一个子程序的范围得以修正。
2. 许多错误发生在构建的范畴之外。
3中最为常见错误的源头,他们是:缺乏应用领域的知识,频繁变动且相互矛盾的需求, 以
及沟通和协调的失误。
3. 大多数的构建期错误是编程人员的失误造成的。95%以上。
4. 让人惊奇的是,笔误(拼写错误)是一个常见的问题根源。
一项研究发现,现在构建阶段产生的错误中,有36%是拼写错误。如果你对此有所怀疑的话,想一想3
个有史以来最昂贵的软件错误——其代价分别是16亿美元,9亿美元和2.45亿美元,都是因为原本正确
的程序中的一个不正确的字符造成的(Weinberg 1983)。
5. 研究程序员所犯错误原因时,错误理解设计这条会经常出现
6. 大多数错误会很容易修正
7. 总结所在组织中对付错误的经验
·Proportion of Errors Resulting from Faulty Construction
不完善的构建过程引发错误所占的比例
随着项目的规模的增长,在构建期间产生的错误所占的比例会下降,然而即使是在巨型项目里面,构建
错误也会占全部错误的45%至75%。
·How Many Errors Should you Expect to Find 你期望能发现多少错误
1. 业界的经验是,在已发行的软件中平均1000行发现1~25个错误。
2. 微软应用程序部门的经验是,内部测试程序大约每1000行代码有10~20个缺陷。而对于已发布产品
则大约是1000行代码0.5个缺陷。
3. Harlan Mills所倡导的“净室开发”的技术,可以获得低至1000行代码3个缺陷(内部测试阶段),以
及1000行0.1个缺陷(产品发布阶段)的错误率。 只有少数几个项目,例如航天飞机的软件,能够达到
每50万行代码0缺陷的水平。这需要使用一个系统的形式化开发方式,同时复查(peer review),以及
统计测试。
4. Watts Humphrey报告称,使用“团队软件开发过程”(Team Software Process, TSP)的开发小
组,可以达到大约1000行0.06个缺陷的水平。TSP把训练开发人员如何避免缺陷放在了第一位。
TSP和净室开发项目的结论从另一个角度证明了软件质量的普遍原则:开发高质量的软件,比开发低
质量的软件然后修正的成本要低廉。 一个使用净室开发技术,经过全面检验合格并拥有8万行代码的项
目,其生产效率相当于平均每个工作月740行代码。而开发经过全面检验合格的代码的业界平均效率大约
是平均没工作月250行~300行代码,这包含了所有非代码的日常开销。
这里我们看到的成本的节约和生产效率提升,其源泉在于使用TSP或者净室技术的项目几乎没有将时
间投入到调试当中。不在调试上面花时间?那真是一个很有价值的目标!
·Errors in Testing Itself 测试本身的错误
通过下列几项工作减少测试用例当中的错误量:
1. 检查你的工作
2. 开发软件的时候就要计划好测试用例
3. 保留你的测试用例
4. 将单元测试纳入测试框架
22.5 Test-Support Tools 测试支持工具
· Building Scaffolding to Test Individual Classes 为测试各个类构造 脚手架
·Diff Tools Diff工具
·Test-Data Generators 测试数据生成器
·Converage Monitors 覆盖率监视器
·Data Recorder/Logging 数据记录器/日志记录器
·Symbolic Debuggers 符号调试器
·System Perturbers 系统干扰器
一类测试支持工具是用来对系统进行干扰的。这类测试支持工具如下多种功能。
1. 内存填充
2. 内存抖动
3. 选择性内存失败
4. 内存访问性检查(边界检查)
·Error Databases 错误数据库
存放以往错误的数据库是另一种强大的测试工具,这样一个数据库既是管理工具,也是技术工具。它
让你能检查重复出现的错误,即时获取已纠正错误和已发现错误之比率,以及跟踪错误的处理状态和严
重级别。
22.6 Improving Your Testing 改善测试过程
1. Planning To Test 有计划的测试
2. Retesting (Regression Testing)重新测试(回归测试)
这次的修改没有给产品引入任何新的错误。为确保软件没有倒退,或者没有“回归”而设计的测
试,被称为“回归测试”。
3. Automated Testing 自动化测试
管理回归测试唯一可行的方法,就是将其变成一个自动化的过程。
自动化测试的好处:
a 自动化测试发生错误的几率比手动测试要小。
b 一旦你把一个测试自动化了,那么你只需少下功夫,就很容易在项目的剩余部分继续实施
自动化。
c 如果测试是自动进行的,那么就可以频繁地运行,看看新的check in的代码是否破坏了原
有的程序。例如daily build,冒烟测试以及极限编程等等。
d 自动化测试可以提高问题刚产生就被发现的可能性,这可能显著减少分析和修正错误所需
要的工作量。
e 由于自动化测试能够提升快速发现修改所引入错误的几率,因此它为大规模代码修改提供
了一张安全网。
f 自动化测试在那些新的,不稳定的技术环境当中特别有价值,因为它提早稀释了环境改变对
系统的影响,而非事后补救。
22.7 Keeping Test Records 保留测试记录
Key Points 要点
1. 开发人员测试是完整测试策略的一个关键部分。
2. 同编码之后编写测试用例相比较,编码开始之前编写测试用例,工作量和花费的时间差不多。但是后
者可以缩短缺陷-侦测-调试-修正这一周期。
3. 即使考虑到了各种可用的测试手段,测试仍然只是良好软件质量计划的一部分。高质量的开发方法至
少和测试一样重要,这包括尽可能减少需求和设计阶段的缺陷。在检测错误方面,协同开发的成效至少
与测试相当。这些方法检测到错误的类型也不同。
4. 你可以根据各种不同的思路来产生很多测试用例,这些思路包括基础测试,数据流分析,边界分析,
错误数据类型以及正确的数据类型等等。你还可以通过猜测错误的方式得到更多的测试用例。
5. 错误往往集中在少数几个容易出错的类和子程序上。找出这部分代码,重新设计和编写他们。
6. 测试数据本身出错的密度往往比测试代码要高。查找这种错误完全是浪费时间,又不能对代码有所改
善,因此测试数据里面的错误更加让人烦恼。要想写代码一样小心地开发测试用例,这样才能避免产生
这种问题。
7. 自动化测试总体来说是很有用的,也是进行回归测试的基础。
8. 从长远来看,改善测试过程的最好办法就是将其规范化,并对其进行评估,然后用从评估中获得的经
验教训来改善这个过程。