持续交付:发布可靠软件的系统方法(四)
- 第 4 章 测试策略的实现
- 4.1 引言
- 4.2 测试的分类
- 4.2.1 业务导向且支持开发过程的测试
- 4.2.2 技术导向且支持开发过程的测试
- 4.2.3 业务导向且评价项目的测试
- 4.2.4 技术导向且评价项目的测试
- 4.2.5 测试替身
- 4.3 现实中的情况与应对策略
- 4.3.1 新项目
- 4.3.2 项目进行中
- 4.3.3 遗留系统
- 4.3.4 集成测试
- 4.4 流程
- 4.5 小结
第 4 章 测试策略的实现
4.1 引言
很多项目只依靠手工的验收测试来验证软件是否满足它的功能需求和非功能需求。即使某些项目有一些自动化测试,但这些测试常常因无人维护或很少维护而过时,所以还是需要大量的手工测试作为补充。本章及本书第二部分中的某些章会讲述如何规划并实施有效的自动化测试体系。我们会为常见的场景提供一些自动化测试策略,并讲述一些用于支撑和进行自动化测试的有效实践。
测试是跨职能部门的活动,是整个团队的责任,应该从项目一开始就一直做测试。质量内嵌是指从多个层次(单元、组件和验收)上写自动化测试,并将其作为部署流水线的一部分来执行,即每次应用程序的代码、配置或环境以及运行时所需软件发生变化时,都要执行一次。手工测试也是质量内嵌的关键组成部分,如演示、可用性测试和探索性测试在整个项目过程中都应该持之以恒地做下去。质量内嵌还意味着,你要不断地改进自动化测试策略。
在一个理想的项目里,项目一开始,测试人员就会与开发人员以及客户一起写自动化测试。这些测试应该在开发人员开始开发要测试的功能之前就写好。这样,这些测试就成了一个可执行的且从系统行为角度描述的规格说明书。当这些测试全部通过以后,也就说明客户所需要的功能已经被完全且正确地实现了。每次有对应用程序的修改时,持续集成系统都会运行这些自动化测试套件,即这些测试套件也是一个回归测试集合。
这些测试不仅仅对系统进行功能测试。容量、安全性及其他非功能测试也应尽早建立,也应该为它们写自动化测试套件。这些自动化测试确保不符合需求的问题能尽早暴露,降低其修复成本。那些对非功能需求的测试让开发人员可以根据该测试所收集到的证据进行重构和构架上的调整,比如“最近对搜索功能的修改引起了性能下降,我们要修改一下解决方案,以确保满足容量要求”
测试策略的设计主要是识别和评估项目风险的优先级,以及决定采用哪些行动来缓解风险的一个过程。好的测试策略会带来很多积极作用。测试会建立我们的信心,使我们相信软件可按预期正常运行。也就是说,软件的缺陷较少,技术支持所需的成本较低,客户认可度较高。测试还为开发流程提供了一种约束机制,鼓励团队采用一些好的开发实践。一个全面的自动化测试套件甚至可以提供最完整和最及时的应用软件说明文档,这个文档不仅是说明系统应该如何运行的需求规范,还能证明这个软件系统的确是按照需求来运行的。
4.2 测试的分类
测试有很多种。Brian Marick提出了如图4-1所示的测试象限,它被广泛地应用于对为了确保交付高质量应用软件而做的各种类型的测试的建模。
4.2.1 业务导向且支持开发过程的测试
这一象限的测试通常称作功能测试或验收测试。验收测试确保用户故事的验收条件得到满足。在开发一个用户故事之前,就应该写好验收测试,采取完美的自动化形式。像验收条件一样,验收测试可以测试系统特性的方方面面,包括其功能(functionality)、容量(capacity)、易用性(usability)、安全性(security)、可变性(modifiability)和可用性(availability)等。关注于功能正确性的验收测试称作功能验收测试,而非功能验收测试归于图中的第四象限。如果对于功能与非功能测试有模糊认识且常常搞不清它们的区别,请参见
技术导向且评估项目的象限。
在敏捷环境中,验收测试是至关重要的,因为它们可以回答如下一些问题。对开发人员来说,它回答了“我怎么知道我做完了呢?”对用户来说,它回答了“我得到我想要的功能了吗?”当验收测试通过以后,它所覆盖到的需求或者用户故事都可被认为是完成了。因此,在理想情况下,客户或用户会写验收测试,因为他们定义了每一个需求的满足条件。时新的自动化功能测试工具,比如 Cucumber、JBehave、Concordion以及Twist,都旨在把测试脚本与实现分离,以达到这种理想状态,并提供某种机制方便地将二者进行同步。在这种方式下,由用户来写测试脚本是可能的,而开发人员和测试人员则要致力于实现这些测试脚本。
总之,对于每个需求或用户故事来说,根据用户执行的动作,一定会找到应用程序中一个中规中矩的执行路径,这称为Happy Path
。Happy Path通常以如下方式来描述:
“假如[当测试开始时,系统所处状态的一些重要特征 ], 当[用户执行某些动作后 ],那么[系统新的状态的一些重要特征 ]。”
有时这称为测试的“given-when-then
”书写模型。
happy表示用户做了完全符合设计者预期的操作。path则是场景或流程的意思。这里面的实际意义就是:如果happy path都没通,那就表示功能必定还没开发完。
然而,除最简单的系统外,任何用例的初始状态、被执行的动作以及执行后的结果状态都会有所不同。有时候,这些变化会形成不同的用例,也就是所谓的Alternate Path
。另外,这些变化还可以引发一些错误处理,从而导致所谓的Sad Path
。很明显,还有很多测试,因为对于其中的可变因素,给予不同的值会得到不同的结果。等价划分分析(equivalence partitioning analysis)和边界值分析可以帮助你得到尽可能小的用例集合,并保证测试覆盖完整的需求。
系统的验收测试应该运行在类生产环境中。例如手工验收测试,它通常是将应用部署在用户验收测试(UAT)环境后进行的。这个环境应该尽可能与生产环境相似(无论是配置还是应用程序的状态)。不过对于那些外部服务来说,我们可能会使用一些模拟(mock)技术。测试人员通过应用程序的标准用户界面来执行测试工作。自动化验收测试也应该运行在类生产环境之上,并且测试用具(test harness)与应用交互的方式应该和真正的用户使用应用的方式相同。
自动化验收测试
自动化验收测试有很多很有价值的特性。
- 它加快了反馈速度,因为开发人员可以通过运行自动化测试,来确认是否完成了一个特定需求,而不用去问测试人员。
- 它减少了测试人员的工作负荷。
- 它让测试人员集中精力做探索性测试和高价值的活动,而不是被无聊的重复性工作所累。
- 这些验收测试也是一组回归测试套件。当开发大型应用或者在大规模团队中工作时,由于采用了框架或许多模块,对应用某一部分的更改很可能会影响其余特性,所以这一点尤其重要。
- 就像行为驱动开发(BDD)所建议的那样,使用人类可读的测试以及测试套件名,我们就可以从这些测试中自动生成需求说明文档。像Cucumber和Twist这样的工具,就是为让分析人员可以把需求写成可执行的测试脚本而设计的。这种方法的好处在于通过验收测试生成的需求文档从来都不会过时,因为每次构建都会自动生成它。
回归测试也是一个特别重要的话题。在前面的象限图中并没有回归测试,因为它是跨象限的。回归测试是自动化测试的全集。它们用来确保任何修改都不会破坏现有的功能,还会让代码重构变得容易些,因为可以通过回归测试来证明重构没有改变系统的任何行为。在写自动化验收测试时,应该时刻牢记,这些测试是回归测试套件的组成部分。
然而,自动化验收测试的维护成本可能很高。如果写得不好,它们会使交付团队付出极大的维护成本。由于这个原因,有些人不建议创建大而复杂的自动化测试集合,比如James Shore就持这种观点。然而,通过使用正确的工具,并遵循好的实践原则,完全可以大大降低创建并维护自动化验收测试的成本,从而令收益大于付出。我们会在第8章讨论这些技术。
同样需要记住的是,并不是所有的东西都需要自动化。对于某些方面的测试来说,用手工方法做更好。易用性测试及界面一致性等方面很难通过自动化测试来验证。尽管有时候测试人员会将自动操作作为探索性测试的一部分,比如初始化环境、准备测试数据等,但探索性测试不可能被完全自动化。很多情况下,手工测试就足够了,甚至优于自动化测试。总之,我们倾向于将自动化验收测试限于完全覆盖Happy Path的行为,并仅覆盖其他一些极其重要的部分。这是一种安全且高效的策略,但前提条件是其他类型的自动化回归测试是很全面的。一般我们将代码覆盖率高于80%的测试视为“全面的”测试,但测试质量也非常重要,单单使用覆盖率这一指标是不够的。我们这里所说的测试覆盖率包括单元测试、组件测试和验收测试,每一种测试都应该覆盖应用程序的80%(我们并不认同60%的单元测试覆盖率加上20%的验收测试覆盖率就等于80%的覆盖率这一天真的想法)。
对于软件开发的各个方面,各个项目之间都会有所不同,你需要监控到底花了多长时间做重复性的手工测试,以便决定什么时候把它们自动化。一个很好的经验法则就是,一旦对同一个测试重复做过多次手工操作,并且你确信不会花太多时间来维护这个测试时,就要把它自动化。更多关于何时做自动化的内容,参见Brian Marick的文章“When Should a Test Be Automated?”
需要写的最重要的自动化测试是那些对Happy Path的测试。每个需求或用户故事都应该有对Happy Path的自动化验收测试,而且应至少有一个。这些测试应该被每位开发人员当做冒烟测试来使用,从而能够为“是否破坏了已有的功能”提供快速反馈。也就是说,这类测试应该是自动化的第一目标。
当你有时间写更多的自动化测试时,很难在Happy Path和Sad Path之间进行选择。如果你的应用程序比较稳定,那么Alternate Path应该是你的首选,因为它们是用户所定义的场景。如果你的应用有较多的缺陷并且经常崩溃的话,那么战略性地应用对Sad Path的测试会帮助你识别那些问题域并修复它们,而且自动化可以保证应用程序保持在稳定状态。
4.2.2 技术导向且支持开发过程的测试
这些自动化测试单独由开发人员创建并维护。 有三种测试属于这一分类:单元测试、组件测试和部署测试
。
- 单元测试用于单独测试一个特定的代码段。因此,单元测试常常依赖于用测试替身(test double,详见4.2.5节)模拟系统其他部分。单元测试不应该访问数据库、使用文件系统、与外部系统交互。或者说,单元测试不应该有系统组件之间的交互。这会让单元测试运行非常快,因此可以得到更早的反馈,了解自己的修改是否破坏了现有的任何功能。这些测试也应该覆盖系统中每个代码分支路径(最少达到80%)。这样,它们就组成了回归测试套件的主要部分。
- 组件测试用于测试更大的功能集合,因此可能会捕获这类问题。当然,它们的运行通常会慢一些,因为它们要涉及更多的准备工作并执行更多的I/O操作,需要连接数据库、文件系统或其他系统等。
- 部署测试用于检查部署过程是否正常。换句话说,就是应用程序是否被正确地安装、配置,是否能与所需的服务正确通信,并得到相应的回应。
4.2.3 业务导向且评价项目的测试
这类手工测试可以验证我们实际交付给用户的应用软件是否符合其期望。这并不只是验证应用是否满足需求规格说明,还验证需求规格说明的正确性。
- 一种非常重要的面向业务且评价项目的测试是演示。在每个迭代结束时敏捷开发团队都向用户演示其开发完成的新功能。在开发过程中,我们也应该尽可能频繁地向客户演示功能,以确保尽早发现对需求规范的错误理解或有问题的需求规范。成功的演示既是福祉,又是灾难,因为用户喜欢尝试新东西,但毫无疑问,他们会提出很多改进建议。此时,客户和项目团队不得不决定他们想在多大程度上对项目计划进行修改,以便响应这些建议。无论什么样的决定,更早的反馈总是比在项目快结束时才得到的反馈要好,因为到那时已经太晚,很难再作出调整了。演示就是项目的核心,因为只有此时你才能说自己的工作让用户很满意,然后才能得到回报
- 探索性测试被James Bach描述为一种手工测试,他说:“执行测试的同时,测试人员会积极地控制测试的设计并利用测试时获得的信息设计新的更好的测试。”探索性测试是一个创造性的学习过程,并不只是发现缺陷,它还会致使创建新的自动化测试集合,并可以用于覆盖那些新的需求。
- 易用性测试是为了验证用户是否能很容易地使用该应用软件完成工作。在开发过程当中很容易发现问题,甚至那些定义软件需求的非技术人员也能轻易发现问题。因此,易用性测试是验证应用程序是否能交付价值给用户的最终测试。有几种不同的方法做易用性测试,比如,情景调查,让用户坐在你的软件前面,观察他们执行常见任务的情形。易用性测试人员收集一些度量数据,记录用户需要多长时间完成任务,注意他们多少次按了错误的按钮,记录他们花多长时间才能找到正确的文本输入框,最后让他们对软件的满意度打分。
- 最后,可以让真正用户使用你的系统进行beta测试。很多网站好像一直处于Beta测试状态。很多有前瞻性的网站(比如NetFlix)会持续发布新功能给特定的用户组,这些用户甚至都不会觉察到。很多组织使用金丝雀发布(参见10.4.4节),即让一个应用程序同时有几个版本运行在生产环境中,而这几个版本之间只是稍有不同,用于比较效果差异。这些组织会收集一些关于新功能使用情况的统计数据。如果分析证明新功能无法带来足够的价值,就会删除它。这种方法可以让新功能不断演进,从而得到更为有效的功能
4.2.4 技术导向且评价项目的测试
验收测试分为两类:功能测试和非功能测试。非功能测试是指除功能之外的系统其他方面的质量,比如容量、可用性、安全性等。正如我们之前提到的,功能测试与非功能测试之间的区别是人为强加的,其依据是非功能需求测试不是面向业务的。这似乎是显而易见的,但是很多项目并不把非功能需求放在与功能需求同等重要的地位来对待,而且可能会更糟糕,他们根本不去验证这些非功能需求。虽然用户很少花时间提前对容量和安全性做要求,但一旦他们的信用卡信息被盗,或者网站由于容量问题总是停止运行,他们就会非常生气。因此,很多人认为“非功能需求”不是一个正确的名字,并建议把它们称为“跨功能需求”(cross-functional requirement)或者“系统特性”。尽管我们也赞同这种提法,但在本书中为了大家都能理解我们在讲些什么,所以仍旧使用“非功能需求”这个术语。无论你把它们称作什么,非功能验收条件应该和功能测试验收条件以同样方式指定为应用程序的需求。
这类测试(用于检查这类验收条件是否都被满足了)和运行这类测试的工具可能与特定于功能验收条件的测试和工具有很大不同。这类测试常常需要很多的资源,比如需要比较特殊的环境来运行测试,并且可能需要专业知识来建立和实现测试,另外它们还通常需要花更长时间来运行(无论这些测试是否是自动化测试)。因此,这类测试的实现一般会比较靠后。即使所有非功能测试都被自动化了,与功能验收测试相比,其运行频率也会更低一些,而且很可能是在部署流水线的最后阶段进行。
4.2.5 测试替身
自动化测试的一个关键是在运行时用一个模拟对象来代替系统中的一部分。这样,应用程序中被测试的那部分与系统其他部分之间的交互可以被严格地掌控,从而更容易确定应用程序中这一特定部分的行为。这样的模拟对象常常就是mock、stub和dummy等。
- 哑对象(dummy object)是指那些被传递但不被真正使用的对象。通常这些哑对象只是用于添充参数列表。
- 假对象(fake object)是可以真正使用的实现,但是通常会利用一些捷径,所以不适合在生产环境中使用。一个很好的例子是内存数据库。
- 桩(stub)是在测试中为每个调用提供一个封装好的响应,它通常不会对测试之外的请求进行响应,只用于测试。
- spy是一种可记录一些关于它们如何被调用的信息的桩。这种形式的桩可能是记录它发出去了多少个消息的一个电子邮件服务。
- 模拟对象(mock)是一种在编程时就设定了它预期要接收的调用。如果收到了未预期的调用,它们会抛出异常,并且还会在验证时被检查是否收到了它们所预期的所有调用。
mock是一种被格外滥用的测试替身。人们很容易错误地使用mock写出不着边际且脆弱的测试,因为大家很容易通过它们来判断代码的具体工作细节而不是代码与其协作者的交互动作。这样的做法使测试很脆弱,因为假如实现细节变了,测试就会失败。
4.3 现实中的情况与应对策略
下面是一些决定写自动化测试的团队可能面临的典型场景。
4.3.1 新项目
新项目有机会实现我们在本书中所描述的理想国。此时,变化的成本比较低,通过建立一些相对简单的基本规则,并创建一些相对简单的测试基础设施,就可以很顺利地开始你的持续集成之旅。在这种情况下,最重要的事情就是一开始就要写自动化验收测试。为了能做到这一点,你需要:
- 选择技术平台和测试工具;
- 建立一个简单的自动化构建;
- 制定遵守INVEST原则[即独立的(Independent)、可协商的(Negotiable)、有价值的(Valuable)、可估计的(Estimable)、小的(Small)且可测试的(Testable)]的用户故事[ddVMFH]及考虑其验收条件。
然后就可以严格遵守下面的流程:
- 客户、分析师和测试人员定义验收条件;
- 测试人员和开发人员一起基于验收条件实现验收测试的自动化;
- 开发人员编码来满足验收条件;
- 只要有自动化测试失败,无论是单元测试、组件测试还是验收测试,开发人员都应该把它定为高优先级并修复它。
相对于项目开发几个迭代后再写验收测试来说,在项目开始就采用这样的流程是比较容易的。当然,必须让团队的每个人(包括客户和项目经理在内)都接受这种做法。最后需要再强调的一点是,应细心编写验收测试,确保它们能正确反映用户故事中从用户视角所定义的业务价值。
遵守我们所说的流程,会改变开发人员写代码的方式。同后补验收测试的项目相比,那些从一开始就使用了自动化验收测试的代码库一般总是有更好的封装、更清晰的表达、更清楚的职责分离和更多的代码重用。这的确是一个良性循环:在正确的时机写测试会产出更好的代码。
4.3.2 项目进行中
引入自动化测试最好的方式是选择应用程序中那些最常见、最重要且高价值的用例为起点。这就需要与客户沟通,以便清楚地识别真正的业务价值是什么,然后使用测试来做回归,以防止功能被破坏。基于这些沟通,你应该能把那些Happy Path的测试自动化,用于覆盖高价值的场景。
另外,让这些测试尽可能覆盖更多的选项也是有用的,即让测试覆盖的范围稍稍宽于通常的用户故事级别的验收测试,比如尽可能多填一些字段,多单击一些按钮来满足测试需求。
由于只对Happy Path进行自动化测试,所以手工测试可能就要相对多一些,以便确保系统完全以预期的方式运行。
当时间比较紧时,你可能无法花大量的精力去写那些有很多交互操作的复杂场景。此时,最好利用各种各样的测试数据来确保一定的覆盖率。
4.3.3 遗留系统
Michael Feathers在他的书Working Effectively with Legacy Code中给出了遗留系统的定义:没有自动化测试的系统就是遗留系统。虽然对这个定义还有一些争议,但它的确有用而且简单。根据这一定义,我们可以遵守一个简单规则,即测试那些你修改的代码
在这种情况下,如果没有自动化构建流程,那么最高优先级的事儿就是创建一个,然后再创建更多的自动化功能测试来丰富它。如果有文档,或能够找到那些曾经或正工作在这个系统之上的成员的话,创建自动化测试套件会更容易一些。然而现实往往并非如此。
这种遗留系统的特点在于:代码通常没有标准组件化,结构比较差。所以修改系统某部分的代码却影响了另一部分代码的事情经常发生。此时,通常比较有效的策略是在测试结束后仔细地验证系统的状态。如果时间来得及,你可以再测试一下这个用户故事的Alternate Path。最后,你还可以写更多的验收测试来检查一些异常条件,或防御一些常见的失效模式(failure mode),或防止不良的副作用。
当软件需要在很多不同的环境上运行时,情况就不同了。此时,自动化测试和类生产环境的自动化部署相结合,会给项目带来巨大的价值,因为可以把脚本直接指向需要测试的环境,从而节省大量的手工测试时间与精力。
4.3.4 集成测试
假如你的应用程序需要通过一系列不同的协议与各种外部系统进行交互,或者它由很多松散耦合的模块组成,而模块之间还有很复杂的交互操作的话,集成测试就非常重要了。在组件测试和集成测试之间的分界线并不十分清晰(尤其当“集成测试”这个词被赋予了太多的意义)。我们所说的“集成测试”是指那些确保系统的每个独立部分都能够正确作用于其依赖的那些服务的测试。
你可以利用写一般验收测试的方式来写集成测试。通常来说,集成测试应该在两种上下文中运行:首先是被测试的应用程序使用其真正依赖的外部系统来运行时,或者是使用由外部服务供应商所提供的替代系统;其次是应用程序运行于你自己创建的一个测试用具(test harness)之上,而且这些测试用具也是代码库的一部分。
我们要确保在正式部署到生产环境之前,应用程序不要与真实的外部系统进行交互,否则就要想办法告诉外部系统,这个应用程序所发送的数据只是用于测试的。一般来说,有两种常见的方法来保证你可以安全地测试自己开发的应用程序,而不必与真正的外部系统进行交互,而且通常要同时使用这两法方法。
- 在测试环境中使用一个“防火墙”将该应用程序与外部系统隔离开来,而在开发过程中,越早这么做越好。当外部系统不可用时,这也是测试应用程序行为的一个好方法。
- 在应用程序中使用一组配置信息,让其与外部系统的模拟版本进行交互。
在理想情况下,服务提供商会提供一个复制版的测试服务,除了性能以外,它可以提供与真正的服务完全相同的行为。你可以在此之上进行测试。然而,在现实世界中,你常常需要开发一个测试用具。比如当:
- 外部系统还没有开发完成,但接口已经提前定义好了(此时你需要有心理准备,因为这些接口很可能会发生变化);
- 外部系统已经开发完了,但是还不能为了测试而部署它,或者用于测试目的的外部系统运行太慢,或缺陷太多,无法支持正常自动化测试的运行;
- 虽然有测试系统,但它的响应具有不确定性,从而导致无法对自动化测试结果进行验证(比如,某个股票市场的实时数据);
- 外部系统很难安装或者需要通过用户界面进行手工干预;
- 需要为涉及外部系统服务的功能写一份标准的自动化验收测试,而这些测试应该一直在测试替身上运行;
- 自动化持续集成系统需要承担的工作量太大且其所需要的服务水平太高,远不是一个仅用于做手工探索性测试的轻量级测试环境所能承受或提供的。
可以把测试用具做得非常全面,但具体做到什么程度,主要依赖于外部服务是否需要记录其状态。如果外部系统是一个有状态系统的话,测试用具也要能够根据收到请求的不同作出不同的反应。此时,黑盒测试是价值最高的测试,并要列出外部系统所有可能的响应,并为每个响应写测试。这个模拟的外部系统需要找到某种方式识别你的请求,并回发正确的响应,假如有个请求不能被外部系统所识别,则应该返回一个异常。
4.4 流程
如果团队成员之间的沟通不畅,写验收测试的成本可能很高,甚至成为一种乏味的体力活。很多项目依靠测试人员来检查收到的需求,遍历所有可能的场景,并设计复杂的测试脚本,作为后续工作的参照。这个流程的产物会让客户进行审批。批准后,测试人员就会依此来测试。
管理待修复缺陷列表
理想情况下,你的应用程序根本不应该有缺陷。如果使用测试驱动开发和持续集成,并有一个全面的自动化测试集,其中包括系统级别的验收测试,以及单元测试和组件测试,在测试人员和用户发现缺陷之前,开发人员就应该能够捕获它们。然而探索性测试、演示以及用户使用的过程中,都可能会发现应用程序的缺陷,这也许是不可避免的。这些缺陷都会放在待修复缺陷列表(backlog)中。
4.5 小结
在很多项目中,测试被认为是一个由一些专职人员负责的独立阶段。可是,只有当测试成为与软件交付相关的每个人的责任,并从项目一开始就被引入并持续进行时,才能产生高质量的软件。测试主要是建立反馈环,而这个反馈环会驱动开发、设计和发布等活动。将测试推迟到项目后期的计划最终都会失败,因为它破坏了产生高质量、高生产率,以及(最重要的)反映项目进展情况的反馈环。
如果每次修改后都能运行一次自动化测试集合,就能建立最短的反馈环。这些测试应该包含从单元测试直到验收测试(包括功能测试和非功能测试)的各种层次的测试。自动化测试应该结合使用手工测试,比如探索性测试和演示。本章的目的是让你更好地理解建立优质反馈所必需的不同类型的自动化测试和手工测试,以及在不同类型的项目中实现这些测试的策略。
在4.1节所描述的原则中,我们讨论了如何定义“完成”。 对于工作的“完成”来说,将测试融入到交付过程的每个环节是至关重要的。因为,我们的测试方法定义了我们所理解的“完成”,而测试的结果是制定项目计划的基石。从根本上讲,测试与“完成”的定义是相互关联的,而测试策略应该在对每个功能特性的测试上都体现出这种关联关系,并确保在整个流程之中到处都有测试活动。