UML软件工程组织

何时应进行自动化测试?
2006.10.08  来自:blog 
  我希望可以自动化实现尽可能多的测试。如果只跑一次测试会使我很不舒服。如果一个程序员改变了代码并引进了一个bug,怎么办?如果我没抓住那个缺陷,只是因为我在变化之后没有进行新的测试,怎么办?我将不感到可怕吗?所以我需要使用自动化测试工具来实现多次的重复测试工作。

  恩,是这样的,当我使用了自动化测试后也没有觉得舒服。测试花费了很长的时间,最终发现是我过度的使用了自动化测试。在我定义的测试里其实只有少量的测试需要自动化测试来帮助完成。多余的自动化测试在运行时是不会发现任何有价值的bug的,毫无意义!

  现在的问题是怎样做合理的自动化测试呢?当我从事测试这项工作,作为一个测试员我一般会为一些产品功能设计一系列的测试。对他们中的每个来说,我需要决定哪个测试应该使用自动化测试来进行。这篇文章描述了我在权衡测试中的看法。

  设想

  为了使我的论点清楚,我一定要避免一次去尝试描述所有可能的测试设想。如果我挑选一个很有用的设想,清楚的描述它,你作为一个读者会很好的了解。然后留下你把论点用于你具体的项目中。下面是我的设想:

  1. 首先,你应该拥有一个固定的自动化的支持。即,自动化工具是可用的。你可以不是一位专家,但是你必须知道怎样使用他们。写好配置文件。我设想你使用已有的工具进行工作,不会去使用新的工具,将一些简单的功能放到配置文件中,或者了解更多的测试自动化。问题是:凭你现有的这些可以证明你的自动化测试一定是正确地吗?现在给你这个答案还为时过早。

  在其他情况下,你可能赞同在一个工程后期增加自动化测试。在本文中没有对到好与不好做辩论,但通过上下文,你会知道自动化测试的价值所在。

  2. 这里只有两种可能性:完全的自动化测试,没有一点的人为操作;全手工进行的自动化测试,使用一次测试后就该扔掉了。这是一个事务上的两个极端。你可以自动化测试那些组织起来很麻烦的部分,但是其余留下的部分做手工。你可能有足够用的很仔细的文献证明能容易再跑一次手动测试。当你深刻理解了从一个极端到另一个极端的时候,你将会清楚的认识到在一个连续的统一体上特殊的测试点应该在哪里。

  3. 自动化测试和手工测试是似是而非的。当然也不是总是这种情况。例如,负载测试经常需要创造大量的使用者的同时操作的情况。如果需要300个测试员同时使用一个产品,很明显是很低效的。所以负载测试需要被自动化。

  4. 通过外部接口所做的测试(“黑箱测试”)。相同的analysis applies在代码级测试——在文章的后面会给出一个简短的例子——但是我将不描述全部细节。

  5. 没有必要必须什么情况都使用自动化测试。经验告诉我们在测试中需要把自动化测试和手工测试完美的结合。

  6. 首先你需要设计好测试然后决定它是否需要被自动化执行。实际上,自动化的需要对设计的影响是很普遍的。这让人很伤心,因为有些时候测试会被减弱只是因为使用了自动化。但是,如果你理解了自动化测试并正确地使用它,它可以给程序带来无害的调整甚至改进。

  7. 你有一定量时间完成你的测试。你应该尽力在规定时间内做更好的测试。在测试开始时,会在是否要测试不那么普通的情况和需要的时间上有一些争论。

  Overview

  我的解决过程用到了这些问题。

  1. 如果自动化执行一次测试需要的时间多于其做简单的手工测试的时间,会多多少时间?
  2. 一个自动化测试过程有一个生命周期,在其生存期它必须赔偿那额外的成本。迟早这次试验可能死吗?什么事件很可能结束它?
  3. 在它的生存期内,它测试出额外bug的可能性(是否在测试的第一时间内发现了bug)?怎样平衡这些不可预测的问题和代价之间的关系?

  如果这些问题没有被彻底的解决,其他小的问题就没有平衡可言。

  第3个问题是必要的,这个我将在大多数细节里继续探索。令人遗憾,一个好的答案需要对一个产品有更好的理解,我将解释应该怎样去把握一个准确的理解。

  使用自动化测试会失去什么?

  通常创造一个自动化测试往往要比手工的进行一次测试所要花费的时间多。(注释一)而费用上的微小的变化取决于这个产品和它的自动化设计。

  如果产品需要经过一个GUI(图形用户接口)被测试,那么你的自动化测试计划手稿中就需要准备驱动GUI接口的部分(本质上要有简单的计划),在很多时候一个自动化测试其花费会和手工测试差不多。

  如果你使用GUI capture/replay工具来记录产品内部接口间的交互并通过这些构建一个脚本的话,自动化测试相对来说是便宜的。但是,当因为错误导致需要重头安排自动化测试的时候,当需要组织整理测试的一些文档记录的时候,当测试进程陷入到bug中并且不断恶化的时候,等等这些,自动化测试就不像人工测试那样的廉宜。这些小细节下引起的成本的追加往往会不容易使人们注意它。

  如果你现在正在对一个编译器进行测试,那么使用自动化测试的成本就会比使用手工的要多一些,应为大部分的时间会为编译器写测试计划,来使编译器根据计划进行编译。这些计划无论会不会被重复使用都的编写。而往往这些计划都不会被重复使用。

  如果你的测试环境对使用自动化测试非常适合,而使用自动化测试的花费只比手工测试多的10%的话。(我认为这种情况很罕见。)这意味着,一旦你自动化实行了十次测试,做一次手工测试——对产品做一次独特的测试——这在用户提出要求前是不可能实行的。如果自动化测试的花费是更大的,那十个自动化测试可以替代运行十个、二十个或者更多的手工测试的话,应该如何掌握和处理呢,我们会发现什么?

  所以,我们第一个测试自动化的问题应该是这样的:

  如果我对它进行自动化测试,我会失去手工测试中的什么?我会丢掉多少bug,自动化测试的严格性?

  依赖于你的设计,其答案是会变化的。假设你是一个电信系统的测试人员,在这里对其质量要求是很高的而且测试的预算会是非常充足的。你的答案可能是“如果我用自动化执行这个测试,我将或许会失去三个手工测试点。但是,我做好了非常漂亮的完整的测试设计工作,我清楚的认识到那些额外的测试只会对现有的测试产生一些价值不高的变化。严格的说,他们应该分别的执行,因为我真的怀疑他们也许会发现新的严重的bug。”对你来说,自动化测试的价值是很低的。

  或者你正在测试一个产品需求和代码在最近几个月内在不停变化的工程的1.0版本。你的答案可能是“嘿!我没有时间去尝试为每一个明显的变化都重新做一次测试。这种情况下,我将会使用自动化来进行测试,同时我要保证其发现新的严重的bug的机会降到最低。”对你来说,自动化测试的价值是很高的。

  这是我衡量自动化测试价值的标准——可以第一时间或预先发现bug——这看上去似乎有些古怪。人们通常是用做这项工作所花费的时间来衡量自动化测试的代价的。我之所以使用这样一个衡量标准是因为自动化测试的目的是在下一次运行中能够发现更多的bug。发现bug的数目体现了自动化测试的价值,因此衡量自动化测试价值的标准也因该统一到这个上面来。(注释2)

  (注释1:有一些例外。例如,或许测试可以被制成模板。用一个工具可以通过处理这些模板中的表单来驱动产品的测试。而向模版表格中填入数据做自动化测试要比做手工测试快的多。看看[Pettichord96]和[Kaner97]的风格,如果用手工测试的花费会很大,我们就不会去牵扯它。但是需要小心:人们往往会低估自动化测试的花费。举个例子来说,将需要输入的数据填写到表单中会是一件非常easy的事,但是自动化测试的结果的验证却会是一件花费很大的事情。感谢Dave Gelperin在这个问题上给我的提示。)

  (注释2:在我和Cem Kaner的谈话中,第一次让我感到应该这样衡量自动化测试的价值。Noel Nyman指出这是约翰 Daly规则的一个特别的情形,就像你总是问题:“我在做测试的时候还有哪些bug没有发现?”)

  在预算上需要注意的地方

  我想让你尽可能的准确估计在你的自动化测试中平均会出现的bug数是多少,并将结果告诉我。你的答案不应该是“0.25”或是“0.25±0.024”。你的答案应该象这样“我会努力减少bug数的出现”或者“我的bug决不会再次出现”。

  稍后,你会被要求估计一个测试的生命周期(生存时间)。这个答案应该是这个样子的:"不会超过软件的发布时间" 或者 "a long time" than "34.6 weeks".

  然后,你会被要求估计在测试生命周期内可以找到多少个bug?答案依然是模糊的。

  最后,你会被要求对自动化测试和手工测试模糊估计的结果做一个比较并做一个结论。

  这样做有用吗?

  回答是肯定的。当你考虑选择谁的时候,需要做出这样的比较——也许不是在表面上做的——尽管是一些少的比较模糊的数据。我的经验是快速的回答这些问题希望可以引导一次优秀的测试,不去管答案是否精确。我在这里倾向于不需要精确的去回答这些问题,但是有用的问题和容易被误解的问题必须要做精确的回答,不要给别人带来误导。

  How Long Do Automated Tests Survive?(一次自动化测试的生命期会有多久?)

  当代码做了改动之后,自动化测试显示出它的价值所在。除了一些极少数类型的测试以外,在代码未作任何改动之前去做重复测试是一个浪费时间而且没有任何效果的做法:它会找出一些bug,但和你第一次做测试所发现的是一样的。(例外,像所用时间测试和压力测试可以概略的用同一中方式来分析。因为简单我忽略了它们。(I omit them for simplicity.))

  但是一个测试不会永远的持续下去。一些观点指出,产品上所做的变动很可能破坏一个测试。这个被破坏的测试将不得不做出修改或是被丢弃。我有一个很合理的近似值是这样的,我们去做一个测试的修改和抛弃已有的测试而重新编写一个新的测试的价值是同样的(注释3)。但是无论在变化后你做什么补救,如果修改和重新编写都不能达到要求的话,我认为放弃它而使用手工测试会比较好一些。

  简而言之,测试的有用寿命看起来像是这一样:

  当决定是否做自动化测试的时候,你必须估计出存在多少会影响测试的代码。如果你的答案是“不多”,那么自动化测试在发现bug上的表现会特别的出色。

  你需要一些背景知识来估计一个测试的寿命。你还需要了解一些代码影响测试的途径。在这里我们从一个特别简单的图表开始:

  (注释3:如果你使用录制工具能够将一次测试重放,那样的价值将会比在开始测试时记录其预期结果要有价值的多。花一些时间和精力在这个上面是在一次测试中是很有必要的。如果你要修改测试脚本,你需要正确地理解这个脚本,修改它测试它,确定你没有可能揭露的所有问题。你会发现新的测试脚本不可能完成老脚本能做的所有功能,所以可以将一个修改测试保留新旧两个测试脚本。如果你的testing effort已经确定下来,那么去修改一个测试要比从头开始写一个测试要好的多。这些将会不影响你在纸上做的工作;那样做会减少自动化测试所需要的花费。这一切的前提是你要清楚的认识和把握一次修复所需要的代价,人们往往会把代价估计不足。)

  假设你的工作是要写一组测试来检测用户是否输入了正确的电话号码,那么你就需要检查是否是输入了有效地阿拉伯数字而非其它字符等等。如果你清楚其中的代码(据我了解很少人会这样)你可以设计一个规划列表将校验电话号码的代码使用高亮显示做出标记。通常称它为 the code under test。这部分代码可以更加完善你的测试任务。

  在大多数时候,你不可能有机会直接运用the code under test。例如:你不可能直接得到确认电话号码的那部分代码,因为它通常会是一个用户的一部分属性,就需要通过用户接口来测试,将与其关联的那部分代码组织起来,使这部分转变到内部程序的数据,并会按照常规将这部分数据表现出来。当然,你也不能直接对表现出来的数据进行检测,因为转变会通过其他的代码来将其转变成在用户界面可见的最后的数据(就像非法的数据会转换成错误信息)。我称这些代码为intervening code——介于测试本身和code under test之间的代码。

  Changes to the intervening code(对介入其间的代码进行变化)

  介入其间的代码是导致测试死亡的主要因素。而且用户图形界面接口较上文提到的那个接口和一些硬件驱动接口相比更是这个样子。例如:假设用户接口要求你输入电话号码,但是现在变化为要求提供一个电话按键区的视觉表现。这时你要使用鼠标敲击号码模拟使用真实的电话。(这是个非常愚蠢的主意,但是这怪异的事情已经发生了。)尽管接口传给了code under test一个正确的值,但是用户界面的变化很可能破坏一次自动化测试,是因为很可能使用者再没有地方输入电话号码了。

  就像另外的一个例子,一个输入的错误用户界面会用其它的方法来告诉用户。它可能会刷新主窗体使其显示红色同时发出特殊的声音来代替弹出的提示信息来告知你不能完成这次操作。但是,如果你的测试是通过测试是不是弹出提示信息来判定的,那么将视这种正常的运行为一个bug。很显然这个测试就没有效果了。

  "Off the shelf "测试自动化工具能做避免测试死亡的有限制的工作。例如:大多数的GUI自动化测试工具都可以忽视文本框大小、位置和颜色的改变。从而把握像上面两段所提到的那些大的改变,但是需要事先定制。这需要在你的工程中有一些人去创建test libraries。这样就要求你,一个测试人员,在编写好测试的特殊术语,尽可能多的忽略用户接口的细节。例如,你的自动化测试可能包含这样一行定制的信息:try 217-555-1212 try是test library程序,它的作用是将电话号码翻译成接口可以知道的术语。如果用户界面接受在输入框中输入字符,try会在其中输入电话号码。如果需要通过显示在屏幕上的特定图形区域键入电话号码时,try也会做到。

  test library 可以有效地将那些不相关信息过滤掉。这样我们就可以详细的准确的测试那些与功能相关的数据。在输入上,增加这些附加信息是intervening code所必须的。在输出上,它们将intervening code中的信息全部压缩到一个很重要的模块中,其中的信息实际上可以当作是Code Under Test的一个延伸。这种过滤可以用左图来描述:

  多数用户界面的变化不会需要对测试做更改,而只需要对test library做相应的修改。应为test Code要比library Code多的多,所以只修改library Code的代价会很低。

  但是,尽管我们有更好的补偿性的代码也不可能将测试从所有的变化中隔离出来。它仅仅是尽可能的去预期所有的事情。所以其中有很多可能性,将来很可能出现一些问题破坏你的测试。你必须问自己这样一个问题:

  在变化中Intervening Code会把测试保护到什么程度?

  你需要估计intervening Code的改变对测试造成影响的可能性。要保证用户界面永远的不会改变是一件不可能的事情,这就使你需要不停的改变自动化测试的脚本以保证测试可以自动的执行。(我不会相信界面冻结后永远不会变化,除非manager答应如果以后每做一个新的修改将会给我100美元)

  如果变化是可能的,你一定会被询问对你的test Library保护你的测试不受其影响正常执行有多大的信心。如果说test Library不能保护测试,那么起码它可以很容易的做出改动以适应变化。如果花费一个半小时的时间可以拯救300个测试,那么所做的一切是值得的。但是,小心:很多人往往低估了维护test Library的困难,特别是在变化后需要手工的对测试test Library进行反复的修改。不应该马上就放弃,抛弃所有的测试类和test Library,从头开始,因为很可能只需要简单的修改就可以完成需要的测试。

  如果你没有test Library——如果你正在使用自动化GUI测试工具来捕获和重放模式——你不要期待会有任何保护。一次对界面的修改会让你的大部分的测试“死亡”。往往不会有足够的时间来允许我们完成对发生变化的测试进行修改,我们不得不在少的花费和短的生命生存期之间做出选择。

  Changes to the code under test(改变测试下的代码)

  Intervening Code不是唯一可以变化的代码,code under test同样可以变化。特别的是,它可以改变使其完全不同的去做某件事。

  例如,假设几年前某个人写了一个关于点话号码的校验测试,为了检查那些不符合要求的电话号码,就像1-888-343-3533。在当时,没有888这样的电话号码,但是现在却存在这样的号码。这样就导致了测试拒绝888号码给出提示,尽管现在这个号码是合法的,但是测试脚本会按照先前的规则进行测试从而拒绝它。解决这件事情可能很简单也可能很复杂。如果你了解问题所在那么这件事是一件很容易的事情:只需要将“888”改为“889”。但是可能很困难对测试做足够的解释去了解测试电话号码整个的方法。或者你没有意识到“888”再现在来说是一个合法的号码,所以你会认为测试理所应当的测出这条Bug。测试在你使用一些假的“Bug”来骚扰开发人员之前是不会固定不变的。

  所以,在决定是否要进行自动化测试之前你同样需要问自己这样的几个问题:

  code under test 行为的稳定行如何?

  注意强调的“稳定性”——只要他们保持外部可试行为相同代码的代码就OK!

  不同类型的产品,不同类型的code under test有不同的稳定性。电话号码实际上还是相当稳定的。再如一个银行帐目管理系统可以说是一个相当稳定的系统,如果每次存100元需要收取30元的手续费那么记录到帐的就是130元,这种关系是稳定的(除非银行改变了收费的标准)。而用户的界界面是相当的不稳定的一个因素。

  增加行为往往是无害的。例如,可能有这样一个检查,测试从一个帐户撤回 $100 由于 $30 生产错误导致操作失败但是帐户余款方面的没有改变。但是,现在测试被重写,增加了一个新的特性:顾客可以根据需要确定是否需要“自动透支保护”功能,它允许用户提取多余他帐户内存在的钱数。这种变化不会破坏现有的测试,只要默认的帐户测试保持原来的行为。(当然,新的测试必须依*新的行为来运行。)

  我们的立足点在哪里呢?

  现在我们知道了自动化测试应该跳远的障碍:必须保证自动化测试的价值要大于采用手工测试的价值。我们需要估计一个测试的生命周期,它可以有机会创造出价值的时间段。现在,我们需要询问一下它可以创造出价值的实际可能性。我们可以期待它能发现什么样的Bug?

版权所有:UML软件工程组织