分类: 项目管理
2008-01-02 12:39:31
在编码之前进行测试 |
级别: 初级 Gary PolliceWorcester Polytechnic Institute 2005 年 4 月 01 日 本文来自于 Rational Edge:本文描述了优先测试的程序设计实践,或称为 TFP,TFP 提出了在您真正书写代码之前为代码生成测试。Pollice 利用扩展的例子对实践进行了说明,并为软件开发人员及他们的团队概括出 TFP 的益处。 以前的文章我讨论了匹配项目和流程的重要性,并劝您考虑一下您所构建的软件环境。这提示我去考虑软件开发是否拥有真正通用的最好实践。我认为不会有。对于现有的实践来说,总是存在至少一个环境,在该环境中,实践不是最佳的。然而,一些实践在大多数情况下是非常好的。其中一种——优先测试的程序设计,或称为 TFP——是本文的主题。 在 开始对此实践进行检验之前,我们需要一些关于什么时候使用它的背景知识。要理解这一点,让我们了解一下项目范围和流程灵活性之间的关系。在这几年与客户打 交道的工作中,我注意到,随着项目范围的扩展,对统一的形式化流程的需求也成比例地增加(参见图 1)。大多数有经验的开发人员积累了了大量的实践和技术,并可以根据工作内容有选择地应用他们。当他们独自完成任务时,他们可以以自己的方式工作。但是, 如果他们在团队中——特别是在企业级——项目经理必须设立更高级别的流程以保持整个团队工作的一致性。通常,该流程必须是相当地稳固的,因为背离它将影响到许多人。
例 如,让我们假设您是软件架构师,总是按照软件需求规范(Software Requirement Specification,SRS)来将项目功能需求描述成一列不相干的“应该有”的条目。但之后您参加了一个关于用例的专题讨论会,并认识到用例更加 正确地描述了为什么系统将对涉众具有意义。尽管您毫无疑问地会采用更好的技术,但是切换到当前项目的用例会导致混乱——除非您能够确保团队中的每个人也进行了切换。 当然,如果您在处理一个大项目中的小项目,您可能会采用不同的技术来优化您的工作,只要这些技术不会干扰更广泛的组织实践。例如,如果大项目要求为用户界面使用专门的 2-D 和 3-D 图形,为绘制这些图形而实现类库的子项目可能采取特殊方法来描述需求。 这不会对其他流程的活动产生负面影响。另一个适于模型驱动开发的子项目可能使用 UML 模型来生成子项目代码的重要部分——没有妨碍整体项目代码的生成规程。
当他们在小项目或子项目中用 他们的方式灵活地工作时,一些开发人员选择应用极限编程(Extreme Programming,XP) 1 方 法的理论。本文不会讨论对项目“进行 XP”的意思,本文将着眼于 TFP,即与此方法论(XP)相关的实践,我认为 TFP 帮助许多软件专业人员创造更好的工作。尽管您依照规范来应用并完善 TFP,我发觉 TFP 很容易理解并且很适合我的工作方式。它还很容易向我的学生示范,并且我已经教授了学生如何在学期项目中利用它。 下面的说明是依据 我如何应用 TFP 的经历阐述的。尽管这些不是详尽的说明,但它也许会提供足够的信息激起您的欲望并鼓励您学习更多的关于 TFP 的内容。为了达到这个目的,我在 参考资料部分提供了许多资源。 TFP(也被称为测试驱动设计或测试驱动开发)实际上是用于实现 XP 所描述的单元测试实践的流程,该流程简单地描述为: 在您写代码之前,确保您拥有失败了的测试。换句话说,写一个将您要撰写的代码的测试。 Ron Jeffries 将流程描述如下:
这似乎非常简单,但您需要满足两个需求才能够成功地采用 TFP。首先,您必须了解如何书写一个好的测试。第二,您必须要求自己在编码之前书写测试。这可能与您在培训时所学到的背道而驰。 在确定要测试的内容后,Jeffries 说,“选择您能想到的所增加的最少的新功能。”这是个好建议。大多数初学者都试图在每个单元测试中填入太多的要素。然而,最少的增量也不总是最好的选择。当您对书写测试很有经验时,您会发现对您来说什么是最舒适最有效的。 对于我来说,一个好的测试取决于一样东西。然而,如何定义“一样东西”要根据我想编写代码的类型而不同。这和设计方法和类的原则类似。您想要整个设计具有高凝聚力,测试也应当具有结合性。 要实施 TFP,您需要有自动化的工具。基本上,TFP 的循环是测试/编码/调试/重新分解/重复。如果没有工具来支持这些活动间的快速转移,您很快就会受挫并放弃了。 支持 TFP 的典型工具是良好的集成开发环境(integrated development environments IDEs)和单元测试框架。我使用带有 JUnit 插件的 Eclipse IDE 来进行所有的 Java 开发。 JUnit 是由 Kent Beck 和 Erich Gamma 为测试 Java 程序而设计的单元测试框架。 3 JUnit 的变化版本支持对其他语言程序的测试,例如用于 C++ 语言的 cppUnit。在本文下个部分,我将用 JUnit 来创建实例。 用
于软件测试的类和程序经常使用一个示例程序,程序接收三个数值,然后将这三个数值作为边长来测试三角形的类型。如果您用三角形的角度进行测试,那么可能的
类型是直角、锐角、钝角或等角三角形。如果您通过三角形的边进行测试,可能的类型是等边、等腰或不规则三角形。要示范如何使用
TFP,我们将创建此示例程序,并包含名为
在此处我必须做出几个决定。首先,我需要决定用作参数的数据类型。我选择最一般的类型,双精度实数。我还要决定如何表示三角形类型。经过考虑,我创建了名为 由于不存在
现在我准备书写第一个测试。Eclipse 可以为 很多测试用例都可用于测试 首先应该测试什么呢?如我上面所提到的,我们可以将三角形的类型按照角度或边来分类,在某些情况下,我们提供的三个数值也许不能组成三角形。先选择哪种可能的情况无关紧要,因此我选择直角三角形。 创建名为
勾股定理(Pythagorean Theorem)告诉我们边长为 3、4 和 5 的三角形是直角三角形,因此可以使用边长 3、4 和 5 来简单地测试。
5
甚至在将 当实施 TFP 时,只添加了足够让测试通过的代码。在这种情况下,我需要真实值,要做到这些的最简单的方法是加入代码示例 4 中的代码。
您可能怀疑是否应该为 现在执行我的第一个测试。在 Eclipse 中执行测试非常简单。选择
现在我知道问题所在了。
接下来要做什么?考虑用非直角三角形?如果输入不同的数,是否还是直角三角形?在写好的测试方法中添加一个关于此问题的测试,如代码示例 5 所示。
现在,当运行测试时,在刚添加的部分出现声明错误。原因很简单。现在
现在,我自然要为
将添加的直角三角形的属性设为私有变量,并增加设置方法。
此 时 JUnit 中再次出现绿色条,因此我可以继续添加新功能。还应该继续测试其他类型的三角形吗?也许,但在某种程度上,我必须处理实数运算在计算机上是不精确的事实。 我知道这要凭经验。所以此时我想看看到现在为止我所书写的代码能否正确处理实数。(我肯定代码不能正确处理,但需要用测试来确定)将代码示例 9 中的测试添加到直角三角形测试中。毫无疑问,JUnit 显示出红色条:测试失败。
现在我要做出一个设计的决定。如何处理精度问题呢?我所能想到的所有解决方案都需要重新分解,返工。经验表明一些返工是不可避免的。至少我现在有一组要求代码必须能够通过的测试,到现在为止,我还没有进入需要大量返工的编码阶段。
6
需要一个delta——可接受的算术错误的量。我想让客户程序测试保持原样,因此添加了默认的 delta,删掉了直角三角形测试的返回语句,并加入了代码示例 10 中所示的代码到
然后,我继续添加测试,每次通过添加新代码、变更现有代码及重新分解的方式来修改代码。最终,实现了一个完整的类,我对之很有信心。我还有可以随时运行的测试。将来我如果改变代码,或其他人改变代码,这些测试会告诉我们是否我们破坏了现有的功能。 在这里,我将停止向您描述所有细节。在继续之前,看看表 1,显示了我所写的测试并按照我所写的顺序排列。还要了解一下在代码改进过程中我所必须做出的决定。
我用了大约 100 行 Java 代码和 70 行测试代码实现了
采用 TFP 会有许多好处,我已经感受过一些好处了。您的工作风格和环境将决定您从实践中得到多少价值。 TFP 最明显的好处就是,它为您写的代码提供测试。让我吃惊的是,今天有多少程序员在编写代码时不去考虑测试。也许一些组织没有清楚地表述出他们对最终代码的质量期望值,许多组织运用了将整个测试负担加到质量保证组上的流程。 事实上, 每个人都要对质量负责——不论您如何定义。要求程序员在将代码加入项目其他部分中时要进行单元测试,这种想法是合理的。如果程序员使用 TFP,他们就不得不对代码进行测试。没有什么办法可以省去测试。刚刚学习如何测试代码的学生经常认为 TFP 不一定会生成好的测试。我告诉他们,必须判断是否“坏的”测试会比根本不测试要好。也许我们将在未来的专栏中探究此问题。 三角形测试实例一共有七个测试方法,但有二十三个截然不同的测试。这还不是全集。我还可以加入更多,但我相信我所开发的测试非常健壮。如果没有书写任何测试,我不会有这样的信心。 您可以通过许多方法来测量代码覆盖率:通过评估代码行或语句的覆盖面、条件覆盖面、分支覆盖面等等。当采用 TFP 时,可以实现 100% 的代码覆盖率。尽管我的那些使用了 TFP 的特殊方法没有提供 100% 的覆盖率(参见下面的多少量足够?部分),但该方法为可能包含缺陷的代码段提供了可以接受的覆盖水平。 测试人员赞同,大约 80% 的代码覆盖率是可以接受的水平。试图实现完全承保常常是浪费时间,如果您花上几个小时去测试错误情况和异常情况,您的受益会减少。 理论上说,如果为满足现有的测试而撰写代码,那么您肯定能够实现对您所写的 每行代码的覆盖。然而,这不太实际。我没有见过任何实验研究证实有 100% 的覆盖率。 重点是,采用 TFP 可以确保您在单元测试中实现相当大的代码覆盖率——可能远远超过我今天所达到的。 设计在发展,我通过经验了解到这一点。当业务环境改变或涉众提出新的需求时,我们需要一些方法来修改现有代码。这就是软件为什么要软且灵活的原因。 当使用 TFP 来驱动您的设计时,实际上您已经采用了测试驱动设计(test-driven design,TDD)的方法来构建具有更加简单更加灵活的体系结构的软件。 在上面的三角形测试实例中,我按照自己书写的测试修改了设计。在测试的响应中添加了常量 当实现对等边三角形和等腰三角形的测试时,我认识到我只用了 delta 值来用于直角三角形的计算。也许我会加入“误差因素”,和 delta 一起来确定是等腰三角形还是等边三角形。但是,由于我现在不需要,所以我可以将它推迟到另外一天——我也许真的需要它的时候。 TFP 或 TDD 是用来补充其他设计工具和技术的实现。随着经验的丰富,我将会找到更多的方法来应用它并能更好的理解它所适用的时间和地点。 当您使用完 TFP 后,您所创建的测试就代表了一组可执行的规范。只要您保持测试和代码的同步(这是实践的关键点),如果有程序员想知道系统能做什么,他或她可以参看测试。 测 试不是您所需的唯一的规范。让客户通过测试来判断您的规范是否正确是不合理的。作为软件工程师,我们知道存在可以用不同方式表示的许多类型的需求。认为一 种类型的需求可以满足每个涉众,或者认为可以很容易地使代码和需求保持同步是很愚蠢的。然而,在此领域,TFP 真的提供了支持。 Mike Ciaraldi 教授,是我在伍斯特工业学院的一个同事,他说要“小规模”地(而非大规模地)衡量项目进展。我们致力于非常小的进展,使它正确,然后继续下一个进展。最终,所有的小进展汇集成大的进展。 如果想使用测试来限制进度,那么我们可以通过撰写优先测试立刻满足要求,并实现代码使之得以工作。我发现采用 TFP 可以防止出现没有内容的分析,以及由于我们不了解所有的限制和所面临的所有可能出现的问题而害怕去撰写代码的情形。 像中国的古老谚语所说的,“千里之行始于足下。” TFP 帮助我们迈出第一步,然后下一步,等等。 TFP 是很容易理解的实践。在本学期,我用了一个小时的课对它进行介绍,提供参考资料,并带着学生完成了一个示例,例如三角形测试程序。在这一个小时的最后,学生们都走出去准备试试运气。 许 多学生很快就掌握了 TFP 并报告说 TFP 改变了他们创建程序的方式。其他人不适应 TFP,我没有强迫他们采用此实践。目前,我希望他们去体验很多软件开发实践并使用适合他们风格的实践。但是,如果在他们的职业生涯中需要 TFP 时,至少他们知道如何使用。如果您是项目经理,您可能希望使用类似的方法,并花费一些时间培训您的团队使用 TFP。
大多数想要采用 TFP 的程序员会问:“多少就足够了?有必要为每行代码和每个方法书写测试吗?” 对此有一些不同的看法。我会测试我所实现的大部分方法。但是,我也不厌烦为 IDE 生成的方法书写测试。 进行测试的多少取决于项目目标和可用的时间。尽管为每个您撰写的方法创建多种测试是非常不错的,但您必须决定您实际能测试多少,结果是否能体现出您所投入的时间。
TFP 是实用的实践,不论您是否将 TFP 作为设计工具来使用。它提供广泛的覆盖面,是相当简单的可防止编码灾难的方法。如果认为要编写的代码很简单,我们会抄近路。但是如果您见过由于一行代码的变更而导致系统的失败,您就会理解多进行测试的必要了。 您 可以在几乎任何项目环境中应用 TFP,不论组织或项目流程中是否指出要使用它。当您的队友看到您代码的质量后,他们也许会问您如何进行改进。您可以简单地分享您的“秘密”,并使之成为 团队流程中的一个部分。如果没有成功地使之加入到流程中,您仍旧可以为您所撰写的测试和代码自豪。
单击此处,下载示例程序Triangle Tester 。
1对 XP 不熟悉的读者可以参考。 2参见。 3要了解更多关于 JUnit 的信息,请访问。阅读该站点的 Test Infected 论文,获得对框架和流程的总览。同时在本文底部的参考资料中列出了一些讲述如何在 TFP 的环境下使用 JUnit 的书籍和论文。 4我们不会展示完整的程序,只展示一个实现功能的类。用 Eclipse 和 JUnit 来对该类进行测试是非常简单的。您可以通过点击文章底部的链接,下载该类的所有代码。 5 勾股定理(Pythagorean Theorem)指 32 + 42 = 9 + 16 = 25 = 52。 6很明显,如果等到开发周期的晚一些的阶段再添加测试,我就会做更多的返工。这是经验之谈。
|