设计测试用例的四条原则
今天是2011年的第一天,2010年就这样匆匆忙忙,紧紧张张地过去了。这一年里来来去去,变化最大的就是很多一起工作了多年的同事离开了,很多都去了"更给力”的地方,呵呵!公司里来来往往是很正常的,想想我最近一次换到“更给力”的地方,那都是5年前了。总之,现在的地方还是挺给力的,好好工作,争取2011年有更大的进步,呱唧呱唧!
测试用例设计的最基本要求:覆盖住所要测试的功能。这是再基本不过的要求了,但别看只是简单的一句话,要能够达到切实覆盖全面,需要对被测试产品功能的全面了解、明确测试范围(特别是要明确哪些是不需要测试的)、具备基本的测试技术(如:等价类划分等)等。那么满足了上述这条要求是不是设计出来的测试用例就是好的测试用例了呢?答案:在理论上是,但在实际工程中还远远不是。之所以理论和实际会有这样的差别,是因为在理论上不要考虑的东东,而在实际工程中是不得不考虑的 - 成本。这里的成本包括:测试计划成本、测试执行成本、自动化测试用例、测试自动化成本,测试分析成本,以及测试实现技术局限、测试环境的Bug、人为因素和不可预测的随机因素等引入的附加成本等。
由于成本因素的介入,决定了工程中设计好的测试用例原则不只有“覆盖住所要测试的功能”这一条,下面是我根据自己的工作经验总结出的其它四条原则,在这里抛砖引玉,希望大家拍砖和指正。这些原则特别是针对那些需要被自动化,并且是要被经常执行的测试用例。
1. 单个用例覆盖最小化原则。
这条原则是所有这四条原则中的”老大“,也是在工程中最容易被忘记和忽略的,它或多或少的都影响到其它几条原则。下面举个例子来介绍,假如要测试一个功能 A,它有三个子功能点 A1,A2 和 A3,可以有下面两种方法来设计测试用例:
- 方法1 :用一个测试用例覆盖三个子功能 -Test_A1_A2_A3,
- 方法2 :用三个单独的用例分别来覆盖三个子功能 - Test_A1,Test_A2,Test_A3
方法1适用于规模较小的工程,但凡是稍微有点儿规模和质量要求的项目,方法2则是更好的选择,因为它具有如下的优点:
- 测试用例的覆盖边界定义更清晰
- 测试结果对产品问题的指向性更强
- 测试用例间的耦合度最低,彼此之间的干扰也就越低
上述这些优点所能带来直接好处是,测试用例的调试、分析和维护成本最低。每个测试用例应该尽可能的简单,只验证你所要验证的内容,不要“搂草打兔子”捎带着把啥啥啥啥都带进来,这样只会增加测试执行阶段的负担和风险。David Astels在他的著作《Test Driven Development:A Practical Guide》曾这样描述,最好一个测试用例只有一个Assert语句。此外,覆盖功能点简单明确的测试用例,也便于组合生成新的测试,在Visual Studio中就引入了Ordered Test的概念。
2. 测试用例替代产品文档功能原则。
通常我们会在开发的初期(Scrum每个Sprint的头两天)用Word文档或者OneNote的记录产品的需求、功能描述、以及当前所能确定的任何细节等信息,勾勒将要实现功能的样貌,便于团队进行交流和细化,并在团队内达成对产品功能共识。假设我们在此时达成共识后,描述出来的功能为A,随着产品开发深入,团队会对产品的功能有更新的认识,产品功能也会被更具体细化,在一个迭代或者Sprint结束的时候最终实现的功能很可能是A+。如此往复,在不断倾听和吸收用户的反馈,修改产品功能,多个迭代过后,原本被描述为A的功能很可能最终变为了Z。这是时候再去看曾经的Word文档和OneNote页面,却仍然记录的是A。之所以会这样,是因为很少有人会去(以及能够去)不断更新那些文档,以准确反映出产品功能当前的准确状态。不是不想去做,而是实在很难!这里需要注意:早期的Word或者OneNote的文档还是必要的,它至少能保证在迭代初期团队对要实现功能有一致和准确的认识。
就没有什么东西能够一直准确地描述产品的功能了吗?答案:当然有,那就是产品代码和测试用例。产品代码实现了产品功能,它一定是准确描述了产品的当前功能,但是由于各种编程技术,如:面向对象、抽象、设计模式、资源文件等等,使得产品代码很难简单地就能读懂,往往是在知道产品功能的前提下去读代码,而不是反过来看代码来了解功能。好的代码会有详细的注释,但这里的注释是对实现代码的解释和备注,并不是对产品功能的描述。这里有一篇很老的博客 Reading Code Is Hard,介绍了如何能够使代码更可读一些编写技巧。
那么就只有测试用例了,测试也应该忠实反映了产品功能的,否则的话测试用例就会执行失败。以往大家只是就把测试用例当作测试用例而已,其实对测试用例的理解应该再上升到另一个高度,它应该是能够扮演产品描述文档的功能。这就要求我们编写的测试用例足够详细、测试用例的组织要有调理、分主次,单靠Word、Excel或者OneNote这样通用的工具是远远无法完成的,需要更多专用的测试用例管理工具来辅助,例如 Visual Studio 2010引入Microsoft Test Manager。
此外,对于自动化测试用例(无论是API或者UI级别的)而言,代码在编写上也应该有别产品代码编写风格,可读性和描述性应该是重点考虑的内容。在测试代码中,当然可以引入面向对象、设计模式等优秀的设计思想,但是一定要适度使用,往往面向过程的编码方式更利于组织、阅读和描述。
3. 单次投入成本和多次投入成本原则。
与其说这是一条评判测试用例的原则,不如说它是一条思考问题的思维角度和原则。成本永远是任何项目进行决策时所要考虑的首要因素,项目中的测试也是如此,对成本的考虑也应该客观和全面的体现在测试的设计、执行和维护的整个阶段中。当你在测试中遇到一些左右为难的问题需要决策时,尝试着从成本角度去分析一下,也许会对你的决策有所帮助。
测试中的成本按其时间跨度可以分为:单次投入成本和多次投入成本。例如:编写测试用例可以看作是单次投入成本,因为编写测试用例一般是在测试的计划阶段进行(Scrum每个Sprint的开始阶段)的,虽然后期会有小的改动,但绝大多数是在一开始的设计阶段就基本上成型了;自动化测试用例也是如此,它也属于是一次性投入;测试用例(包括:手工和自动化测试用例)的执行则是多次投入成本,因为每出一个新版本Build时都要执行所有的测试用例(或者进行BVT测试仅执行高优先级的测试用例)、分析测试结果、调试失败测试用例、确定测试用例的失败原因(产品缺陷、测试用例缺陷、测试框架缺陷还是随机问题导致了测试用例的失败),以验证该版本整体质量是否达到了指定的标准。
之所有要引入单次和多次成本的思考,是希望能够通过区分测试中不同活动对测试成本的影响,从而进行帮助我们合理布局在不同阶段的投入和做出正确的决策,以保证在有限可负担测试成本的前提下,最大限度地有效开展测试工作。例如,当我们意识到了,测试用例的设计和自动化属于是一次性投入,而测试用例的执行则是反复多次的投入时,就应该积极思考如何能够提高需要反复投入的测试执行的效率,在一次投入和需要多次活动需要平衡时,优先考虑多次投入活动的效率,其实这里是有很多工作可以做。
例如:第一条原则-单个用例覆盖最小化原则 - 就是一个很好的例子,测试A功能的3个功能点A1,A2和A3,从表面上看用Test_A1_A2_A3这一个用例在设计和自动化实现时最简单的,但它在反复执行阶段会带来很多的问题:
- 首先,这样的用例的失败分析相对复杂,你需要确认到底是哪一个功能点造成了测试失败;
- 其次,自动化用例的调试更为复杂,如果是A3功能点的问题,你仍需要不断地走过A1和A2,然后才能到达A3,这增加了调试时间和复杂度;
- 第三,步骤多的手工测试用例增加了手工执行的不确定性,步骤多的自动化用例增加了其自动执行的失败可能性,特别是那些基于UI自动化技术的用例;
- 第四,(Last but not least)将不相关功能点耦合到一起,降低了尽早发现产品回归缺陷的可能性,这是测试工作的大忌。例如:如果Test_A1_A2_A3是一个自动测试用例,并按照A1->A2->A3的顺序来执行的,当A1存在Bug时,整个测试用例就失败了,而A2和A3并未被测试执行到。如果此时A1的Bug由于某些原因需要很长时间才能修复,则Test_A1_A2_A3始终被认为是因为A1的Bug而失败的,而A2和A3则始终是没有被覆盖到,这里存在潜在的危险和漏洞。当你在产品就要发布前终于修复了A1的Bug,并理所当然地认为Test_A1_A2_A3应该通过时,A2和A3的问题就会在这时爆发出来,你不得不继续加班修复A2和A3的问题。不是危言耸听,当A2/A3的代码与A1的Bug修复相关时,当你有很多如此设计的测试用例时,问题可能会更糟… …,真的!:(
综上所述,Test_A1_A2_A3这样的设计,减少地仅是一次性设计和自动化的投入,增加地却是需要多次投入的测试执行的负担和风险,所以需要决策时(事实上这种决策是经常发生的,尤其是在设计测试用例时)选择Test_A1_A2_A3还是Test_A1、Test_A2和Test_A3,请务必要考虑投入的代价。
4. 使测试结果分析和调试最简单化原则。
这条原则是实际上是上一条- 单次投入成本和多次投入成本原则 - 针对自动化测试用例的扩展和延续。在编写自动化测试代码时,要重点考虑如何使得测试结果分析和测试调试更为简单,包括:用例日志、调试辅助信息输出等。因为测试用例的执行属于多次投入,测试人员要经常地去分析测试结果、调试测试用例,在这部分活动上的投入是相当可观的。有时候,测试框架提功能的一些辅助API等就可以帮助很好实现这个原则。例如:Coded UI Test就提供了类似的API,详见 - VS 2010 测试功能学习(18) – Coded UI Test三个必知的函数,来辅助基于Coded UI框架实现的自动化测试用例有更好的调试体验。
测试理论为测试工作指明了大的前进方向,在实际工程中还需要我们不断地“活化”这些理论,使理论和实践更好的契合在一起。在我看来,软件工程项目不论成败和好坏,对我们每个参与者都是无比宝贵的。作为有心人,从中我们体会到很多书本上不曾提到过的东西,只要不断地去观察、体会和总结,你会有更多自己的认识、理解和发现。有很多人写书称赞,代码之美、测试之美,其实工程项目也是很美,只是看你能不能更客观地去看待它。