副标题#e#
假如这样说不会(令您)很疾苦的话,请设想您是一名任职于一家 2002 年 早期建设的公司的开拓人员。在款子的驱动下,您和您的团队接到了一项任务, 纵然用最新且最强大的 Java™ API 构建一个大型的数据驱动的 Web 应用 措施。您和公司打点层都刚强不疑地相信这就是最终将被称为火速进程 的对象 。从第一天起,您就用 JUnit 构建测试,且把它作为 Ant 构建进程的一部门尽 大概频繁地运行。还将配置一个按时任务在夜间运行构建。在接下来的某个时刻 ,有人会下载 CruiseControl,不绝增长的测试套件会在每次签入时运行。
时至今天
颠末已往几年的尽力,您的公司已经开拓了一个复杂的 代码库和一个同样复杂的 JUnit 测试套件。一切都很正常,直到约莫一年前, 测试套件包括了 2000 个测试,同时人们开始留意到运行构建进程用时高出三个 小时。在此之前的几个月,由于 CI 处事器资源告急,您在代码签入时通过 Continuous Integration(CI)遏制运行单位测试,并将测试切换到夜间运行, 这使得之后的早晨时间很是告急,于是开拓人员尽力去弄清楚是什么堕落以及为 什么堕落。
这些天,好像测试套件整晚少少高出一次运行,为什么会这样呢?因为它们 费时太多!没人会仅仅为了弄大白系统是否运行精采而几个小时守在哪里。另外 ,整个测试套件都是在晚上运行,不是吗?
由于测试运行得太不频繁, 它们经常布满了错误。因而,您和您的团队开始质疑单位测试的代价:假如它们 对代码质量那么重要,那又为什么会让人这么头痛呢?你们的结论是:单位测试 有其重要的浸染,但必需要能用一种更为火速的方法运行它们。
实验测 试分类
您所需要的是一个将构建转换到一种更为火速状态的计策。您需要这样一种 办理方案,使一天傍边运行测试的次数高出一次,并使测试套件规复到要用三个 小时才气完成构建之前的程度。
为完整地规复整个测试套件,在试图提出一个计策之前,很有须要弄清楚通 用术语 “单位测试” 的寄义。诸如 “我家有一个动物” 和 “我喜欢车” 这 样的表述并不很详细,“我们编写单位测试” 也是一样。这年初,单位测试能 代表一切。
就拿之前有关动物和车的表述来说:它们导致了更多的疑问。譬喻,您家有 哪种动物?是一只猫、一条蜥蜴照旧一头熊?“我家有一头熊” 和 “我家有一 只猫” 截然差异。同样,当和汽车销售员攀谈时,只说 “我喜欢车” 没什么 用处。您喜欢哪种车:赛车、卡车照旧观光车?任何一个谜底都能带来截然差异 的功效。
同样,对付开拓人员测试来说,凭据范例 将测试分类也是很有用的。这样做 可以或许实现更为准确的语言,而且能使您的团队以差异的频率运行差异的测试范例 。为了制止运行所有 “单位测试” 所需的令人惊骇的三小时构建时间,分类是 要害。
三种范例
测试套件可以形象地分为三层,每一层代表一种差异的开拓人员测试范例, 该测试范例由其运行时间的是非抉择。正如在图 1 中看到的那样,每一层都增 加了总的构建时间,要么增加了运行时间,要么最终增加了编写时间。
图 1.测试分类的三个条理
底层由运行时间最短的测试组成,可以想象的到,它们也最易于编写。这些 测试占用的代码量也是最少的。顶层由更高级此外测试组成,这些测试占用了应 用措施更大的部门。这些测试有一点难于编写,执行时间也要长得多。中间层是 处于这两个极度中间的测试范例。
三种范譬喻下所示:
单位测试
组件测试
系统测试
#p#副标题#e#
让我们别离来看一下。
1. 单位测试
单位测试断绝地 验证一个或多个工具。单位测试不处理惩罚数据库、文件系统或 任何大概耽误测试运行时间的内容;因而,从第一天就可以编写单位测试。事实 上,这也正是 JUnit 设计简直切目标地址。单位测试的断绝观念有无数的模仿 工具库作后援,这些库便利了将一个特定的工具从其外部依赖项间断绝出来。而 且,单位测试可以或许在真正要测试的代码前编写 —— 由此有了测试优先开拓 的 观念。
单位测试凡是易于编写,因为它们并不依靠于架构的依赖项,且凡是运行得 很快。缺点是,独立的单位测试只能包围稍显有限的代码。单位测试的重大代价 在于:它们使开拓人员可以或许在尽大概低的层面上担保工具的靠得住性。
#p#分页标题#e#
由于单位测试运行得如此之快且如此易于编写,代码库中应包括很多单位测 试,而且应该尽大概多地运行它们。在执行构建时,应该常常 运行它们,不管 是在呆板上,照旧在 CI 情况的上下文中(这意味着,代码一经签入 SCM 情况 ,就要运行单位测试)。
2. 组件测试
组件测试验证多个彼此浸染的工具,但它打破了断绝的观念。由于组件测试 处理惩罚一个架构的多个条理,所以它们常常用于处理惩罚数据库、文件系统、网络元素 等。同样,提前编写组件测试有点难,所以将其包括至一个真正的测试优先/测 试驱动的场景中是很大的挑战。
编写组件测试要花更长的时间,因为它们比单位测试所涉及的对象要多。另 一方面,由于其宽阔的范畴,它们实现了比单位测试更广的代码包围率。虽然它 们也要花更多时间运行,所以同时运行许多的组件测试会显著地 增加总的测试 时间。
很多框架有助于测试大型架构组件。DbUnit 是这类框架的一个典规范子。 DbUnit 可以或许很好地处理惩罚在测试状态间成立一个数据库这样的巨大性,因而它会 使编写依赖于数据库的测试变得较为简朴。
当构建的测试耽误时,凡是都预示着包括了一个大型的组件测试套件。由于 这些测试比真正的单位测试运行时间长,因而不能一直运行它们。相应地,在 CI 情况中这些测试可以至少 每小时运行一次。在签入任何代码前,也应该总在 一个当地开拓人员呆板上运行这些测试。
验收测试
验收测试 和成果测试雷同,差异之处在于,抱负环境下,验收 测试是由客户或最终用户编写的。正如成果测试一样,验收测试也像最终用户测 试那样举办。Selenium是一个备受瞩目标验收框架,它利用欣赏器测试 Web 应 用措施。Selenium 在构建进程中可以是自动运行的,就像 JUnit 测试一样。但 Selenium 是一个新平台:它不利用 JUnit,在利用方法上也不相似。
3. 系统测试
系统测试端到端地 验证一个软件应用措施。因而,它们引入了一个更高级别 的架构巨大度:整个应用措施必须为要举办的系统测试而运行。假如是一个 Web 应用措施,您就需要会见数据库以及 Web 处事器、容器和任何与运行系统测试 相关的设置。其遵循这样的原则,即大大都系统测试都在软件生命周期的较后周 期中编写。
编写系统测试是个挑战,也需要大量的时间来实际地执行。而另一方面,就 架构性代码包围率来讲,系统测试是一件极为划算的工作。
系统测试和成果测试很相似。所差异的是,它们并不仿效用户,而是模仿出 一个用户。与在组件测试中一样,此刻建设了大量的框架来为这些测试提供利便 。譬喻,jWebUnit 通过模仿一个欣赏器来测试 Web 应用措施。
用 jWebUnit 照旧 Selenium 呢?
jWebUnit 是为系统测试设计的一个 JUnit 扩展框架;因而它需要您来编写测试。Selenium 在验收测试和成果测试 方面表示卓越,差异于 jWebUnit,它使非措施员也可以或许编写测试。抱负环境下 ,团队可以同时 利用两种东西来验证应用措施的成果。
实现测试分类
所以,您的单位测试套件就是名副其实的包罗单位测试、组件测试和系统测 试的套件。不只如此,在查抄了这些测试后,您此刻知道构建花了三个小时的原 因是:绝大部门时间都被组件测试所占用。下一个问题是,如何用 JUnit 实现 测试分类?
有几种方法可选,但这里我们只存眷于个中两种最简朴的方法:
按照所需种类建设定制的 JUnit 套件文件。
为每种测试范例建设定制目次。
用 TestNG 举办测试分类
用 TestNG 实现测试分类相当简朴。用 TestNG 的 group 注释凭据种类在逻辑上分别测试,这与将适当的 group 注释应用到所 需测试中一样简朴。这样一来,运行一个特定范例实际上就是将一个相应的组名 称通报给一个测试运行措施,如 Ant。
建设定制套件
可以利用 JUnit 的 TestSuite 类(属于 Test 范例)来界说很多相互归属 的测试。首先,建设一个 TestSuite 实例,并为其添加相应的测试类或测试方 法。然后,可以通过界说一个叫做 suite() 的 public static 要领,在 TestSuite 实例中指定 JUnit。包括的所有测试随后将在单个运行中执行。因而 ,可以通过建设单位 TestSuite、组件 TestSuite 和系统 TestSuite 来实现测 试分类。
譬喻,清单 1 中显示的类建设了一个 TestSuite,其持有 suite() 要领中 所有的组件测试。请留意此类并不长短常特定于 JUnit 的。它既没有扩展 TestCase,也没有界说任何测试用例。但它会反射性地找到 suite() 要领并运 行由它返回的所有测试。
清单 1. 用于组件测试的 TestSuite
#p#分页标题#e#
package test.org.acme.widget;
import junit.framework.Test;
import junit.framework.TestSuite;
import test.org.acme.widget.*;
public class ComponentTestSuite {
public static void main(String[] args) {
junit.textui.TestRunner.run(ComponentTestSuite.suite());
}
public static Test suite(){
TestSuite suite = new TestSuite();
suite.addTestSuite(DefaultSpringWidgetDAOImplTest.class);
suite.addTestSuite(WidgetDAOImplLoadTest.class);
...
suite.addTestSuite(WidgetReportTest.class);
return suite;
}
}
界说 TestSuite 的进程简直需要欣赏现有的测试,并将它们添加到相应的类 中(即,将所有的单位测试添加到一个 UnitTestSuite 中)。这也意味着,由 于在一个给定分类中编写新测试,不得不将它们凭据必然的措施添加到适当的 TestSuite 中,虽然,还需要从头编译 它们。
运行独立的 TestSuites,然后试着建设单一的 Ant 任务,Ant 任务挪用正 确的测试集。可以界说一个 component-test 任务,用于组织 ComponentTestSuite 等,正如清单 2 中所示:
清单 2. 只运行组件测试的 Ant 任务
<target name="component-test"
if="Junit.present"
depends="junit-present,compile-tests">
<mkdir dir="${testreportdir}"/>
<junit dir="./" failureproperty="test.failure"
printSummary="yes"
fork="true" haltonerror="true">
<sysproperty key="basedir" value="."/>
<formatter type="xml"/>
<formatter usefile="false" type="plain"/>
<classpath>
<path refid="build.classpath"/>
<pathelement path="${testclassesdir}"/>
<pathelement path="${classesdir}"/>
</classpath>
<batchtest todir="${testreportdir}">
<fileset dir="test">
<include name="**/ComponentTestSuite.java"/>
</fileset>
</batchtest>
</junit>
</target>
抱负环境下,还需要有挪用单位测试和系统测试的任务。最后,在想要运行 整个测试套件时,应该建设一个依赖于所有三种测试种类的第四项任务,如清单 3 中如示:
清单 3. 用于所有测试的 Ant 任务
<target name="test-all" depends="unit-test,component-test,system-test"/>
建设定制 TestSuite 是实现测试分类的一个快速办理方案。这个要领的缺点 是:一旦建设新测试,就必需通过编程将它们添加到适当的 TestSuite 中,这 很疾苦。为每种测试建设定制目次更具扩展性,且答允不 颠末从头编译就添加 新的颠末度类的测试。
建设定制目次
我发明,用 JUnit 实现测试分类最简朴的要领是将测试在逻辑上分别为与其 测试范例相应的特定目次。利用这项技能,所有的单位测试将驻留在一个 unit 目次中,所有的组件测试将驻留在一个 component 目次中,依此类推。
譬喻,在一个生存所有未分类测试的 test 目次中,可以建设三个新的子目 录,如清单 4 所示:
清单 4. 实现测试分类的目次布局
acme-proj/
test/
unit/
component/
system/
conf/
为运行这些测试,必须至少界说四个 Ant 任务:为单位测试界说一个,为组 件测试界说一个,依此类推。第 4 项任务是一个利便的任务,它运行所有三种 测试范例(如 清单 3 所示)。
该 JUnit 任务和 清单 2 中界说的任务很是相似。所差异的是该任务 batchtest 方面的一个细节。此时,fileset 指向一个详细的目次。在清单 5 的例子中,它指向 unit 目次。
清单 5. 用于运行所有单位测试的 JUnit 任务的批量测试方面
<batchtest todir="${testreportdir}">
<fileset dir="test/unit">
<include name="**/**Test.java"/>
</fileset>
</batchtest>
#p#分页标题#e#
请留意,这个测试只运行 test/unit 目次下的所有测试。当建设了新的单位 测试(或针对此问题的任何其他测试),只需要将它们放到该目次下,一切就准 备妥当了!比起需要将一行新代码添加到 TestSuite 文件并举办从头编译,这 样照旧几多简朴了一点。
问题办理了!
回到最初的场景中,假设您和您的团队认为利用特定目次是针对构建时间问 题的最具扩展性的办理方案。该任务最坚苦的处所是查抄及分派测试范例。您重 构了 Ant 构建文件并建设了 4 项新任务(为单个的测试范例建设了三项,为运 行所有这些测试范例建设了一项)。不只如此,您还修改了 CruiseControl,从 而只在(代码)签入时运行真正的单位测试,并以小时为基本运行组件测试。在 进一步查抄之后,发明系统测试也可以按小时运行,所以您建设了一个将组件测 试和系统测试一起运行的特别任务。
最终功效是,测试天天都运行许多次,您的团队可以或许更快地发明集成错误 — — 凡是在几个小时之内。
虽然,建设火速性构建并未办理全部问题,但它在确保代码质量方面确实扮 演了至关重要的脚色。测试运行得越发频繁了,针对开拓人员测试代价的记挂成 为一段遥远的影象。别的,更重要的是,此刻 2006 年您的公司得到了极大的成 功!