JUnit实战(第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-01 14:20:49

点击下载

作者:[美]Petar Tahchiev,Felipe Leme,Vincent Massol,Gary Gregory 著

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

JUnit实战(第2版)

JUnit实战(第2版)试读:

前言

到目前为止,测试仍然是人们所能找的能确保交付的软件正常运行的最好办法。本书是4年来在测试领域研究和实践的成果。实践来自我的IT咨询背景——我曾先后在Octo Technology公司和Pivolis公司任职;研究则来自我在晚上和周末所从事的开源开发工作。

自从1982年我的早期编程生涯开始,我就对编写能帮助开发者写出更好代码并且提高开发速度的工具感兴趣了。这一兴趣引导我进入了软件咨询、质量改进等领域。这些日子,我在建立持续构建(continuous-build)平台,同时也在探索最佳开发实践。这两者都需要完善的测试集来支持。测试与编码活动越接近,从代码获得反馈就越快速,所以我对单元测试很感兴趣。单元测试与编码活动如此接近,以至于现在单元测试像编写代码一样成为开发活动的一部分。

这个背景使得我参与了以下这些与软件质量有关的开源项目:● Cactus,用于对J2EE组件进行单元测试((http://

jakarta.apache.org/cactus/);● Mock Objects,用于对任何代码进行单元测试(http://

www.mockobjects.com/);● Gump,用于持续构建(http://jakarta.apache.org/gump/);● Maven,用于构建和持续构建(http://maven.apache.org/);● Pattern Testing,使用AOP(Aspect-Oriented Programming)来

检查构架和设计规则,这是一个概念验证性质的项目(http://

patterntesting.sf.net/)。

由于参与了这些项目,也就有了《JUnit in Action》这本书的面世。

没有人想编写糟糕的代码。我们都希望写出可以正常工作的代码——我们都以自己的代码为荣。但是,常常事与愿违。你是否经常听到这样的话:“我们是想要些测试的,但我们在压力下没有足够的时间去写。”“我们一开始是写了单元测试的,但两个星期后我们就没有动力继续下去了,再往后我们就放弃了编写单元测试。”

本书将向你展示为了编写高质量的代码所需要掌握的各种工具和技能。本书手把手地教你如何高效地运用工具,避免常见的陷阱。本书将使你具备编写可以正常工作的代码的能力,还将帮你把单元测试引入日常开发活动,并建立起一套步骤,使你可以按部就班地写出稳健的代码。

最重要的是,本书向你展示了如何控制你的软件的熵(译注:指随着软件的增长或修改,软件变得复杂和紊乱的程度),而不是反过来被它控制。我想起了拉丁作家Lucretius(公元前94年至公元前55年)在他的《On the Nature of Things》作品中写到的一些诗句(我就不列出拉丁文原文了):

It is lovely to gaze out at the churning sea from the safety of the shore when someone else is out there fighting the waves, not because you’re enjoying their troubles, but because you yourself are being spared.

这正是当你意识到自己被精良的测试集“武装”起来时你将体验到的感觉。你会看到,别人还在蹒跚挣扎;并且你会感到欣慰,因为你拥有测试,可以用来阻止任何人(包括你自己)破坏你的应用程序。Vincent Massol前言

作为一个屡获殊荣的数学家,我无法容忍平庸。这就是数学所教给我的——不要停止,直到你把它完成,并且不仅要用好的方法,而且要用最好的方法。

当我开始编写软件时,我发现这条原则同样适用。我认识一些同事,他们对自己的工作非常马虎随便,并且我也看到了他们因为自己的疏忽大意而深受其害。他们不耐烦地完成他们的任务,从不担心他们开发的软件质量,更不用说寻找可能的最佳解决方案。对于那些家伙,重用相同的代码就意味着,在任何他们需要相同代码的地方简单地复制和粘贴它即可。我看到了不耐烦地尽快完成任务是如何导致相同的任务一次又一次地被重新打开,因为编写代码时所产生的bug和问题。

值得庆幸的是,那类同事已经很少见了。大多数我的朋友都是值得我向他们学习的人。我曾有机会为惠普公司工作,不仅与技术团队合作,而且还与各级项目经理共事,并从他们那里我学到了提供高质量软件产品的秘密。

后来,我加入了Apache软件基金会(Apache Software Foundation,ASF),在那里我有机会与一些全球最顶极的软件开发人员一起工作。我研究了他们编写代码、编写测试用例以及在我们之间分享信息的最佳做法和习惯,并且我可以为惠普的一些最大客户将我学习到的知识和技能应用到各个项目中。

我逐渐对确保软件产品的可持续质量的问题发生了兴趣。然后,我在2008年春天遇到了Vincent Massol和Felipe Leme。我曾在ASF与他们俩一起为了Cactus框架工作。Vince建议我为他5年前撰写的畅销书编写最新的修订版。虽然这个计划是确定的,但是我还需要一些兴趣相投的伙伴来帮我一起实现它。这时候我联系了Felipe Leme和Gary Gregory。他们俩都愿意帮忙编写一些章节。

在这之后事情的进展就顺利多了,带着修订Vince作品的最初目的,我们花了一年半的时间来编写新版本。如果有人在一开始就告诉我这项工作会有多么艰难,我肯定以为他在吓唬我。这就是为什么我觉得我需要向Manning团队表示衷心感谢的原因——他们使得整个写书的过程轻松了许多。

现在这本书已经出版,并且你可以把它捧在手中,我希望你会喜欢它。为了完成这本书,我们经历了一段艰难的旅程,但是现在它终于完成了。我知道你会从我们的书中学到很多新的东西,因为我确信你会提高你的软件质量——你已经迈出了第一步。Petar Tahchiev致谢

我们要感谢所有为本书的创作做出贡献的朋友。首先,要感谢Manning公司的Michael Stephens和Marjan Bace,如果不是因为他们,就不会有这本书的面世。其次,要感谢我们的内容编辑Sebastian Stirling,本书逻辑清晰、文字流畅,这在很大程度上要归功于他。我们也要感谢Megan Yockey、Steven Hong、Mary Piergies、Karen Tegtmeyer、Katie Tennant、Linda Recktenwald以及Manning公司的其他相关人员,他们为此所付出的努力远远超出我们的想象。特别感谢Ivan Ivanov,在本书即将付梓之前,他还为本书进行了最后一次技术上的校对。

我们还要感谢所有在本书编写过程中抽时间阅读原稿并指出问题的开发人员。在从原稿到出版的过程中,以下参与审稿的人员为本书提供了珍贵无价的建议,使它成为一本值得读者投入时间和金钱的书:Robert Wenner、Paul Holser、Andy Dingley、Lasse Koskela、Greg Bridges、Pratic Patel、Martijn Dashorst、Leonardo Galvao、Amos Bannister、Jason Kolter、Steffen Müller、Marion Sturtevant、Deepak Vohra、Eric Raymond、Andrew Rhine、Robert Hanson、Tyson S. Maxwell、Doug Warren、David Strong、John Griffin和Clint Howarth。

最后,我们还要向那些订阅Manning Early Access Program(MEAP)版的用户表示真挚的感谢;他们在作者在线(Auther Online)论坛所留下的阅读反馈,极大地提高了本书最终印刷成品的品质。

谢谢大家!Petar Tahchiev

我想先感谢我的家人,非常感谢你们总是那么信任我。特别感谢我的姐姐,她让我真正理解了“勇气”这个词的意义。还要非常感谢我的表兄Ivan Ivanov,他促使我开启了我生命中这段疯狂的计算机旅程。我也感谢我人生中所有的英语老师——感谢你们。感谢Vincent Massol使得这一切成为可能,要不是因为你的辛勤工作,就不会有这本书的存在。最后,感谢一起参与此书编写的Felipe Leme和Gary Gregory,你们两位真是太棒了。我期待每天能见到你们。Felipe Leme

首先,我要感谢那些对我职业生涯发展功不可没的人,从而使我参与此书的编写成为可能:我的父母,他们深知教育的重要性;我的中学老师们(特别是Ivo先生),他们给我打下了良好的写作基础,并激发我对科学的兴趣;Leonardo Galvão,他对我在《Java Magazine》上的文章的犀利点评帮助我成为一名更优秀的作家。然后,要特别感谢Petar,不仅邀请我参与本书的编写,而且他的真知灼见和辛勤努力使得这个项目成为现实。最后,感谢我的妻子和孩子们,他们一直给予我支持和鼓舞。Vincent Massol

回想2003年,《JUnit实战》是我写的第一本书。当时我完全不知道写作过程将会持续多久。最终我花了18个月(几乎是怀孕生子的两倍时间)才完成了这本书。当你完成一项长期任务时,最令人难忘的是,历经漫长投入收获果实的那一刻喜悦。这7年中,我总是带着与最初相同的不安,跟踪了解《JUnit 实战》的销售情况;7年后,我很高兴看到第1版仍在持续销售中。然而,现在到了修订此书的时候了。虽然本书中相当一部分内容还是应时的,但是大部分实例和框架却已经有了发展,新的应用和框架也应运而生。Petar赞同重新编写第2版,为这本书赋予第二次生命,这对我来说真是莫大的欣慰。你将会看到Petar、Felipe和Gary为此书的修订完成了非常出色的工作——添加了大量令人激动的新主题。

干得好,伙计们!Gary Gregory

我想感谢我的父母,他们开启了我的人生之旅,为我提供了接受良好教育的机会,并给予我充分的自由选择自己的道路。我永远感谢我的妻子Lori和我的儿子Alexander,他们鼓励我参与这样一个项目。一路上,我不断学习,有机会与许多非常杰出的人士共事。最后,我要感谢本书的其他作者以及Manning公司的所有成员,他们的支持、专业精神和宝贵建议都给我留下了深刻的印象。关于本书

欢迎阅读《JUnit实战》第2版!如果你拿起这本书,那么你就有可能是一名关注软件产品质量的Java开发人员。或许你使用过早期版本的JUnit框架,或许你使用过其他的测试框架,亦或许这是你第一次接触软件测试。无论你之前拥有什么样的经历,你都可能对提高软件过程和软件质量感兴趣。本书旨在帮助读者掌握软件测试的基础知识和相关技能。软件测试包括了许多解决具体任务的项目、测试应用的不同组件和层。在软件测试中,最核心的角色是JUnit框架。大约10年前,Erich Gamma和Kent Beck编写了这个框架,事实上它已经成为Java测试领域的测试标准。JUnit框架最新的4.x版本不仅仅是3.x版本的修订版,还带来了更多的变化。如果你还没有听说过JUnit框架,那么根据书名,你可能会期待找到一个行之有效的老框架的新版本。但事实并非如此。不同于JUnit的早期版本,4.x版本中引入一种新的方法并重新编写了整个框架。因此,也就有必要对本书的第一版进行修订了。

在《JUnit实战》第2版中,我们介绍了使用JUnit框架测试项目必须掌握的核心概念。但是,这并不是本书全部的内容!本书不仅教用户如何使用JUnit框架编写自己的测试用例(test case),而且还针对在编写代码的过程中如何使得代码可以测试给出建议。本书还介绍了基础的软件开发原则,如测试驱动开发(TDD)等。它也将逐步指导用户如何测试每一个典型Java EE应用的每一层,例如,前层,使用诸如Selenium和JSFUnit此类的扩展工具;业务层,使用诸如Cactus、mock objects和stub此类的工具;最后的数据库和JPA层,使用诸如DBUnit之类的工具。

本书分为几个部分,其目的是按照由浅入深、由易到难的顺序对JUnit进行讲解。第1部分包括了头几章,介绍了JUnit框架的技术背景,高度概括了其构架,并展示了一个非常简单的HelloWorld示例应用程序,以帮助读者搭建和运行自己的测试环境。在简短介绍JUnit后,我们又利用几章的篇幅逐一介绍了所有重要的概念和框架组件,并深入讨论了每一个组件的功能。

本书的第2部分介绍了不同的测试技术:mock方法和容器内方法,还介绍了一些新工具,以便创建我们需要的模拟对象。本书的第3和第4部分详细讨论了第三方工具/JUnit扩展,我们使用它们来测试不同应用的不同层。此外,本书也提供了几个附录,以帮助读者快速转换到最新版本的JUnit,并且能够轻松地集成自己喜欢的IDE。本书结构

第1章帮助你快速入门。本章通俗易懂地介绍了测试是什么、如何有效地进行测试、如何编写自己第一个测试用例。通过对这一章的阅读,你会充分意识到测试是自然而然产生的,它总是在开发期间发生。

第2章深入讨论了JUnit构架及其构成。在这一章中,介绍了大部分的JUnit常用功能。

在第3章中,我们开始创建一个应用程序实例。你将掌握几种设计模式并使用它们创建应用程序。在后面的章节中,我们还将阐述如何利用第2章介绍的JUnit功能有效地测试应用程序。

第4章着眼于几个重要方面:单元测试的必要性、已有软件测试的不同特点、不同测试类型之间的区别。我们也针对如何搭建不同的开发和测试环境给出了有效的建议。

在第5章中,我们将讨论测试质量的话题。我们继续探讨几个关键问题,如如何改善测试、如何提高测试覆盖面、如何设计应用程序构架以便使应用程序更易于测试。本章的最后简要地介绍了测试驱动开发(TDD)方法。

第6章详细介绍了stub技术,通常在无法获得系统资源时使用这种技术来模拟系统资源。在这一章中,我们提供了一个示例:通过使用Jetty嵌入式servlet容器来替换servlet容器。

第7章阐述了另一种在测试中加入模拟对象的方法:mock objects。当你处于封闭的API下进行编程,并且你无法修改或实例化可用的资源时,这种技术就非常有用。在这一章中,我们提供了一个示例:使用两大最主流的框架EasyMock和JMock来模拟一个Servlet并测试它。

第8章简要地介绍了当缺少重要的系统对象时我们所能采用的最后一种方法:容器内测试(in-container testing)。我们在这一章中介绍这种方法,以便我们在第14~16章中能够展开这个话题,讨论真实环境下的容器内测试示例。作为本书第2部分的总结章,第8章中也比较了前面章节所讨论的几种方法:stub、mocks和容器内测试。

第9章是本书第3部分的开篇之章。在这一部分中,我们的重点是JUnit与不同构建框架的集成。特别是在这一章中,我们介绍了Ant构建框架。我们告诉你如何自动执行测试,以及如何使用执行结果生成有效的、美观的报告。最后,我们使用Ant框架运行前面章节中的一些示例。

第10章继续讨论引入构建框架并使JUnit与之集成的方法,但在这一章中我们来看一下Maven。

第11章专门介绍持续集成(Continuous Integration,CI)理论——以一种持续的方式构建项目并执行测试,以确保没有任何变化会影响项目。我们进一步介绍了两大实践持续集成的主流软件项目:CruiseControl和Hudson,我们也借此机会,将前面章节中的一些示例导入到这两个工具中,设置并执行它们。

从第12章开始进入本书的最后一部分。这一部分讨论了各种JUnit扩展,借助它们可以增强测试框架,从而就能完成一些通常不可能完成的具体任务。在这一部分中,我们还介绍了典型应用程序的所有层,并解释了如何测试这些层。第12章重点关注Web应用程序的表示层,所以我们介绍了HtmlUnit和Selenium工具,并明确说明了如何使用它们。

第13章继续关注Web应用程序的表示层,但在这一章中,我们将聚焦于最难的话题之一:Ajax。我们不但详细介绍了Ajax是什么、为什么它很难测试,还阐述了各种测试方案。最后,我们介绍了JsUnit项目,并就测试Google Web Toolkit(GWT)应用程序给出了一些特别的建议。

第14章探讨了另一种测试表示层的方法:即已在第8章中提到的容器内测试(in-container testing)。为此,我们介绍了有史以来的第一个容器内测试框架:Apache Cactus项目。

第15章介绍了几种特别适用于测试JSF应用程序的方法。这一章详细说明了如何使用另一种称为JSFUnit的新兴工具——一种新的容器内测试框架,它基于Apache Cactus,专门用来测试JSF应用程序。

第16章是专门为那些对OSGi应用程序感兴趣的读者而准备的。在这一章中,首先简单地介绍了OSGi的概念,然后介绍了JUnit的扩展JUnit4OSGi,并提供了几种使用模拟和容器内测试来测试OSGi应用程序的方法。

从第17章开始的最后3章主要讨论了数据库测试。在第17章中,我们全面详细地介绍了一个称为DBUnit的项目。我们还深入剖析了几种测试数据库的方法,无论采用了什么持久化技术。

第18章揭开了JPA测试的所有秘密:测试多层应用程序和JPA持久层应用程序。

第19章是本书的最后一章。在这一章中,我们讨论了几种提高测试效率的方法。我们还介绍了一个新项目来帮助你测试Spring应用程序:Unitils。本书约定

以下印刷约定适用于全书:● Courier字体用于表示所有的代码列表;● Courier字体用于正文中表示某些代码;● 黑体用于强调和介绍新术语;● 注解用于代码中进行内部注释,突出显示重要的概念或代码区

域。如果注解带有编号(如❶),那么其编号将随后出现在正文

中。

此外,在代码列表中,你可能偶尔还会看到粗体代码。● 粗体代码——这里使用粗体有两个目的:一是突出显示一些

Java关键字(便于读者阅读),二是突出显示两个或多个代码列

表之间的不同。代码下载

读者可以通过Manning公司网站上的图书主页中的链接www.manning.com/JUnitinAction SecondEdition或www.manning.com/tahchiev,下载到本书相关的源代码。该下载页面包含了一个目录结构,列出了不同章节对应的所有子模块。每个子文件夹包含了一个用来编译和打包的构建脚本,读者可以执行与之相关的测试。关于如何安装应用程序的说明已包含在下载文件中的README文件中。

关于源代码,有几点要特别说明一下。起初我们想采用一个大型应用程序,可以全面展示应用层中的各种测试方法。后来,我们意识到使用这样一个大型应用程序所要面临的各种困难,于是我们转而采用目录结构表示法,每一章都拥有与之对应的源代码示例。全书的源代码示例根据章名被划分到不同的子文件夹中。所有的源代码示例都包含一个Maven构建脚本,并且其中有一些源代码示例还包含了一个Ant构建脚本。为了运行书中的所有示例,读者需要在计算机中安装Maven2。作者在线交流

购买《JUnit实战(第2版)》也就意味着你获得了免费访问Manning出版公司所运营的一个私有论坛的权利,在这个论坛上你可以对本书做出评价,可以提出技术问题,并从作者和其他读者那里获得帮助。你可以从www.manning.com/JUnitinActionSecondEdition注册并访问该论坛。这个页面提供了注册后如何访问论坛的信息,也说明了你可以获得什么帮助以及论坛的行为规则。

Manning对读者的承诺是,提供一个让读者之间以及读者和作者之间得以有效沟通的场所,但并不承诺作者们的参与程度,作者们对本书论坛的参与仍然是自愿的(无偿的)。我们建议你向作者们提出一些具有挑战性的问题,这样他们才会有兴趣经常参与。只要本书尚未绝版,读者就可以通过出版商的网站访问作者在线(Author Online)论坛和以前讨论的存档。关于书名

In Action丛书的特点是理论(介绍、概述等)与实践(教你如何动手的各种示例)相结合,其目的在于帮助读者学习和记忆。根据认知科学方面的研究,人们记住的事物往往都是他们在自我探索过程中发现的事物。

虽然Manning公司内部没有一位是认知科学家,但是我们深信,要使得学习的效果永久,就必须经历探索、实践以及复述学到了什么这几个阶段。人们理解并记住新事物,这也就是说,只有在积极探索新事物之后人们才能掌握它们。人类在实践中学习。“实例驱动”是In Action丛书的一个基本要素。它鼓励读者尝试,实践新代码,探索新构想。

关于本书的书名,还有一个更现实的理由:我们的读者都很忙。他们因工作需要或者为了解决问题而寻求书籍的帮助。他们需要的图书是这样的:不仅可以让他们快速上手,而且还可以让他们在想学的时候只学他们想学的内容。他们需要可以在实践(In Action)中帮助他们的图书。该丛书就是专门为这类读者设计的。关于作者

Petar Tahchiev是一名软件工程师,担任了Apache软件基金会(Apache Software Foundation)的Jakarta项目管理委员会(PMC)成员。多年来,他一直是Jakarta Cactus的开发主力,也参与了Apache Maven开发团队。此外,他也是JCP成员和Bulgarian Java User Group(BGJUG)的负责人,经常在OpenFest、ApacheCON、CommunityONE和许多其他会议上发表演讲。Petar在保加利亚出生并长大,并以优异的成绩毕业于索非亚(Sofia,保加利亚首都)大学的数学系。他曾在德国和荷兰的公司(如Unic、惠普)任职多年,现在他回到可爱的索非亚,重点发展他自己的公司Phamola,他的公司主要为客户在如何通过技术赢得优势方面提供帮助和建议。Petar撰写了本书的第1~11章、第14~16章以及附录A~D。

Felipe Leme是一名软件工程师,非常热爱TDD(Test-Driven Development)、Java和电脑。11岁时,他得到了他人生中的第一台电脑,1996年他开始学习Java,2000年他编写了他的第一个JUnit测试用例。自从1997年在Campinas州立大学(Unicamp)获得计算机工程学士学位,他就几乎只使用Java了,同时他也在许多方面为社区做出了贡献:开源项目(如DbUnit)的提交者(Committer)、会议(如JavaOne)演讲者、JCP个人成员、java.net上的博客和撰稿人。Felipe撰写了本书的第17~第19章。经常往返于巴西的圣保罗和美国的加利福尼亚之间后,他终于定居在旧金山的湾区(Bay Area),与他的妻子、孩子们和寄居蟹生活在一起。

Vincent Massol在利用几年的晚上时间创建了Jakarta Cactus和Codehaus Cargo并参与Apache Maven开源项目后,现在正享受着全职开发XWiki的乐趣,XWiki是一个提供最顶级企业维基服务的开源项目。Vincent也是XWiki SAS公司的CTO,这家公司主要提供XWiki开源项目方面的服务。他是本书第一版的主要作者。Vincent生活在法国巴黎,可以通过www.massol.net在线联系他。

Gary Gregory拥有超过20年的面向对象语言的经验,包括Smalltalk、Java、XML、各种数据库技术。Gary曾任职于Ashton-Tate、ParcPlace-Digitalk和其他几家软件公司,目前他在Seagull软件公司从事现有系统集成(Legacy Integration)应用程序服务器的开发。他是Apache软件基金会和Apache Jakarta项目管理委员会的积极参与者,定期为各种Apache Commons项目贡献力量。Gary在法国巴黎出生和长大,获得洛杉矶加利福尼亚大学的语言学和计算机科学学士学位。Gary撰写了本书的第12~第13章和附录E。他和他的妻子、儿子伴随着高尔夫俱乐部、各类冲浪板生活在洛杉矶。可以通过http://www.garygregory.com

联系他。第1部分认识JUnit

欢迎阅读《JUnit实战(第2版)》!JUnit是一个由Kent Beck和Erich Gamma于1995年年底着手编写的框架。自此以后,JUnit框架日益普及,现在已经成为单元测试Java应用程序的事实上的标准。

本书是第2版。《JUnit实战》的第1版非常畅销,由Vincent Massol和Ted Husted于2003年编写,其内容是基于JUnit 3.x版本的。

我们涵盖了JUnit最新的版本4.6,讨论了许多第1版尚未介绍的功能。与此同时,我们关注其他一些有趣的测试代码的方法:mock objects、JUnit扩展、测试应用程序的不同层,等等。

这一部分从探索JUnit本身开始。在本书后面的章节中,我们将专注于另一些工具和方法。

第1章快速介绍了测试的概念。你需要从基础知识入手逐步深入。在本章的后半部分,我们会直接跳到代码内容,查看如何编写简单的测试、运行它并看到运行结果。

第2章介绍了JUnit最核心的内容。我们构建了一个稍大型的项目,并分析其代码。我们不仅解释了JUnit的概念、Widget和内部构成,也为你展示了编写测试用例的最佳做法,并利用构建的项目对它们进行了说明。

第3章重点介绍了测试。我们描述了各种各样的测试以及它们所适用的情况。我们还探讨了不同的平台(如开发、生产等),并展示了哪种测试和哪种情况能够在这些平台上最好地执行。

第1部分的最后一章致力于提升你的测试技巧。我们告诉你如何衡量测试覆盖面以及如何提高测试覆盖面。我们也解释了如何在编写测试之前生成可测试的代码,如何在动手编写代码之前编写测试。第1章JUnit起步

Never in the field of software development was so much owed by so many to so few lines of code.

在软件开发领域中,从来没有这样的事情:少数几行代码对大量代码起着如此重要的作用。——Martin Fowler

本章重点● 探索JUnit● 安装JUnit● 编写第一个测试● 运行测试

所有的代码都需要进行测试。

在开发期间,我们所做的第一件事是运行程序员自己的“验收测试”。我们编码、编译并运行。当我们运行时,我们就在进行测试。测试可能只是点击一个按钮,看它是否能弹出预期的菜单。然而,每天我们都要进行编码、编译、运行和测试。

当我们测试时,我们经常会发现各种问题——尤其是第一次运行时。于是,我们重新编码、编译、运行和测试。我们中的大多数人会迅速形成一种非正式的测试模式:添加一条记录、查看一条记录、编辑一条记录以及删除一条记录。手动运行诸如此类的小测试集是非常容易做到的,所以我们会不断重复这个操作。

有些程序员喜欢这类重复性的测试。在经历深入的思索和艰难的编码后,这类重复性的操作可以带来一段愉快的小憩。当我们小小的点击测试终于成功时,一股成就感便会油然升起:搞定了!我搞定了!

但有一些程序员却不喜欢重复性的工作。与其手动运行测试,他们宁愿创建一个小程序来自动运行测试。编写测试代码是一回事,而运行自动测试是另一回事。

如果你是一名编写测试代码的开发人员,那么这本书就是为你而准备的。我们将为你展示创建自动测试是多么简单、有效,甚至有趣。[1]

如果你是一名深受测试影响的开发人员(test-infected),那么这本书同样适合你。我们在第1部分介绍了基础知识,然后在第2、第3和第4部分继续探讨实际工作中的各种疑难杂症。1.1 证实它能运行

一些开发人员认为自动测试是开发过程中非常重要的一部分。你无法证实一个组件能够运行,除非它通过了一系列全面的测试。有两名开发人员觉得这类单元测试如此重要,以至于值得为它编写一个框架。1997年,Erich Gamma和Kent Beck针对Java创建了一个简单但有效的单元测试框架,叫做JUnit。他们的工作遵循了SUnit的设计,而SUnit是Kent Beck早期为Smalltalk创建的一个框架。DEFINITION [2]框架是一个应用程序的半成品。框架提供了一个可复用的公共结构,可以在多个应用程序之间进行共享。开发人员将框架融入到他们自己的应用程序中,并且加以扩展以满足他们特定的需求。框架与工具包的不同之处在于,框架提供了一致的结构,而不只是一组简单的工具类。

如果你对这些名字似曾相识,那很正常。Erich Gamma就是为我[3]们带来经典之作《模式设计》的四大作者之一。同样,Kent Beck由于他在软件开发方面的开创性成就“极限编程”(http://www.extremeprogramming.org

)而广为人知。

JUnit(http://www.junit.org

)是SourceForge网站上的一款开源软件,根据IBM通用公共许可证(Common Public License)1.0版本进行发布。通用公共许可证对商业用户是友好的:人们可以随同商业产品分发JUnit,而没有很多繁琐的手续和限制。

JUnit迅速成为Java中开发单元测试的事实上的标准框架。被称为xUnit的相关测试框架,正在逐渐成为任何语言的标准框架。ASP、C++、C#、Eiffel、Delphi、Perl、PHP、Python、REBOL、Smalltalk和Visual Basic都有了对应的xUnit框架,这里只是列举了一些而已。

JUnit团队并没有发明软件测试,甚至也没有发明单元测试。起初,单元测试这个术语只是用来描述一项检查单个工作单元的行为的测试。

随着时间的推移,单元测试这一术语的运用领域扩展了。例如,IEEE已经将单元测试定义为“单个硬件、软件单元或一组相关单元[4]的测试”。

在本书中,单元测试只是指窄义上的一种测试:检查与其他单元隔离的单个单元。我们重点关注的是程序员应用在他们自己代码中的小型渐增测试。有时我们把这种测试称为Programmer Test(http://c2.com/cgi/wiki?ProgrammerTest),以区别于品质保证测试(QA)和客户测试。

以我们的角度来看,一个典型的单元测试通常可以描述为:“确保方法接受预期范围内的输入,并且为每一次测试输入返回预期的值”。

以上描述要求我们通过方法的接口来测试方法的行为。如果我们将x值传给方法,那么它会返回y值吗?如果我们改为将z值传给方法,那么它会正确地抛出异常吗?DEFINITION 单元测试检查一个独立工作单元的行为。在Java应用程序中,独立工作单元经常是(但不总是)一个独立方法。相比之下,集成测试和验收测试检查的是各种组件如何交互。一个工作单元就是一项任务,不直接依赖于其他任何任务的完成。

单元测试通常关注的是一个方法是否遵循了它的API契约中的条款。就像人们在特定条件下同意交换某种商品或服务时所签署的书面合同,API契约是一份由方法签名而生成的正式协议,方法需要它的调用者提供特定的对象引用(Object Reference)或原始类型数值(Primitive Value),然后返回一个对象引用或原始类型数值。如果方法不能遵守契约,测试将抛出异常,于是我们就说方法已经破坏了它的契约。

在这一章里,我们从零开始介绍如何为一个简单的类创建单元测试。我们首先编写一个测试以及运行该测试的最小框架,以便你能够理解我们是如何处理的。然后我们再通过JUnit向你展示,正确的工具可以如何使生活变得更加简单。DEFINITION API契约是一种观点:把一个应用程序编程接口(API)当作是在调用者与被调用者之间的正式协议。通常,单元测试通过证明预期的行为来帮助定义API契约。API契约的概念源于因Eiffel编程语言而流行的“Design by Contract”(http://archive.eiffel.com/doc/manuals/technology/contract)的实践。1.2 从零开始

为了我们的第一个示例,我们创建一个简单的Calculator类,可以将两个数相加。Calculator类为了客户端提供了一个API,但并没有包含一个用户界面,如代码1.1所示。

代码1.1 用于测试的Calculator类

虽然这里没有列出文档,但Calculator的add(double,double)方法的意图显然是接受两个double值并以double类型返回它们的和。虽然编译器能够告诉我们它通过了编译,但是我们也应该确保它在运行期间能够正常工作。单元测试的核心原则是“任何没有经过自动测[5]试的程序功能都可以当作不存在”。add方法代表了Calculator的一个核心功能。我们拥有了一些能够实现该功能的代码,现在缺少的只是一个证明实现能够正常工作的自动测试。add方法如此简单怎么可能出错?目前add方法的实现非常简单,以致不可能出错。假如add只是一个不重要的工具方法,那么我们可能就不会直接测试它。在这种情况下,如果add出错,那么针对使用add的所有方法的所有测试都将会出错。于是,add方法将被间接测试,虽然如此,但还是算被测试过了。在Calculator程序中,add不只是一个方法,而且是一项程序功能。为了确保程序正确运行,大部分开发人员都会期待一个针对add功能的自动测试,无论实现看起来多么简单。在某些情况下,我们可以通过自动功能测试或自动验收测试来证明程序功能。关于软件测试的一般内容,可以参阅本书的第3章内容。

在这个时候进行任何测试看起来都会有些困难。因为我们甚至都没有用户界面可以输入一对double值。我们可以编写一个命令行小程序,它能够在我们输入两个double值后显示运行结果。这样一来,就同时测试了我们自己输入数字及求和的能力了。这可比我们想要的还要多。我们想要知道的是,这个工作单元是否能够对两个double值求和并且返回正确的值,而并没想测试程序员是否能够输入数字!

与此同时,如果想花大力气测试我们的工作成果,那么也应该尽量使得这一份投入物有所值。在我们编写add(double,double)方法时,知道它能够正常工作这固然很好,但是我们真正想知道的是,在交付应用程序的其他部分后或者在任何时候进行了后续的修改,这个方法是否依然够能正常工作。如果我们把这些需求放在一起考虑,那么我们就会产生一个想法:为add方法编写一个简单的测试。

这个测试程序可以将已知的值传给方法,并判断运行结果是否与我们的预期一致。我们也可以随后再次运行该程序,以确保该方法能够随着应用程序的增长而持续正常工作。我们能够编写的最简单的测试程序是什么样的呢?如代码1.2所示的CalculatorTest程序怎么样呢?

代码1.2 一个简单的CalculatorTest程序

第一个CalculatorTest非常简单。它创建了Calculator的一个实例,传递给它两个数字,并且检查运行结果。如果运行结果与我们的预期不一致,那么我们就在标准输出设备上输出一条消息。

如果现在我们编译并运行这个程序,那么测试将会正常通过,同时一切看上去都非常顺利。但是如果我们修改代码使其报错,那么将会发生什么呢?为了找出错误信息,我们将不得不仔细地盯着屏幕。我们可能不必提供输入信息,但是我们仍然在测试我们自己监控程序输出的能力。我们想要测试的是代码,而不是我们自己!

在Java中,表示错误条件的传统做法是抛出异常。所以,我们要抛出异常来表示测试失败。

与此同时,我们或许还想针对其他尚未编写的Calculator方法运行测试,如subtract或multiply。转向模块化的设计可以使得捕捉和处理异常更为容易,也可以使得以后扩展测试程序更为简单。代码1.3展示了稍好一点的CalculatorTest程序。

代码1.3 稍好一点的CalculatorTest程序

来看代码1.3,在❶部分,我们把测试转移到了它自己的testAdd方法中。现在要观察测试做了些什么更加容易了。我们稍后也可以增加更多的方法,编写更多的单元测试,而不会使main方法变得更加难以维护。在❷部分,我们修改了main方法,以便在发生错误时输出栈跟踪信息,然后,如果发生任何错误,就抛出一个总结性的异常结束运行。

既然你已经理解了一个简单的应用程序及其测试,那么你就能够发现,即使这种不重要的类及其测试都可以从少量的脚手架代码(scaffolding code)中受益——我们创建脚手架代码来运行和管理测试结果。由于应用程序变得越来越复杂、涉及的测试也越来越多,继续构建和维护我们自己自定义的测试框架已成为一个负担。

接下来,我们退一步,来看一下单元测试框架的一般情况。1.3 理解单元测试框架

单元测试框架应该遵循以下几条最佳实践规则。CalculatorTest程序中一些不起眼的改进就突出体现了所有单元测试框架应该遵循(按照我们的经验)的三大规则:● 每个单元测试都必须独立于其他所有单元测试而运行;● 框架应该以单个测试为单位来检测和报告错误;● 应该易于定义要运行哪些单元测试。“稍好一点”的CalculatorTest程序虽然向以上规则靠拢了,但仍然存在着不足。例如,为了使每个单元测试能够真正独立运行,就应该在不同的类实例中运行它们,理想的情况是在不同的类加载器(class loader)实例中运行它们。

我们现在能够通过增加一个新的方法并且在main中增加一个对应的try/catch块来增加新的单元测试。显然,这是一个进步,但是在真正的单元测试集中,这样做还远远不够。经验告诉我们,很大的try/catch块会带来诸多维护问题。我们很可能遗漏一个单元测试并且还对此毫无意识!

如果我们可以增加新的测试方法并继续正常工作,那真是太好了。但是,程序又是如何知道要运行哪些方法呢?好吧,我们应该有一个简单的注册过程。一个注册方法至少可以盘点哪些测试正在运行。

另一种方法是使用Java的reflection和introspection功能。程序可以检查自身并决定要运行任何遵循某种命名协定的方法,例如,以“test”开头的方法。

对于单元测试框架而言,使增加测试变得更加简单(上文中提到的第3条规则)听起来像是另一条不错的规则。为了满足这条规则(通过注册或自我检测),还需要编写大量的支持代码,但这将是值得的。虽然起初的工作量会很大,但是随着每次我们增加一个新的测试,所有的努力都会得到回报。

令人高兴的是,JUnit团队为我们解决了这个麻烦。JUnit框架已经支持自我检测方法。它也支持针对每个测试使用不同的类实例和类加载器实例,并逐个报告每个测试的所有错误。既然你已经明白了你为什么要需要一个单元测试框架,那么就让我们进一步来了解JUnit。1.4 JUnit的设计目标

JUnit团队已经为框架定义了3个不相关的目标:● 框架必须帮助我们编写有用的测试;● 框架必须帮助我们创建具有长久价值的测试;● 框架必须帮助我们通过复用代码来降低编写测试的成本。

在第2章中,我们会再次谈论到这几个目标。

接下来,在我们进入实战之前,先来看看如何安装JUnit。1.5 安装JUnit

为了使用JUnit来编写应用程序测试,就需要将JUnit的JAR文件添加到项目的编译classpath(类路径)和执行classpath中去,可以按照以下步骤操作。

从网站http://www.junit.org

上下载JUnit发布包(junit-4.6或者更新版本)。JUnit发布包包含了几个测试例子,你可以运行它们来熟悉JUnit测试的执行。

将JUnit发布包的ZIP文件解压缩到计算机操作系统上的一个目录(如Windows系统上的C:\或者UNIX操作系统上的/opt/)下。

在这个目录中,解压缩操作将会为了刚下载的JUnit发布包创建一个子目录(如Windows系统上的C:\junit4.6或者UNIX操作系统上的/opt/junit4.6)。

现在你可以准备运行随着JUnit发布包提供的测试例子了。JUnit配有了用来全面查看测试结果的各种Java程序,包括一个基于文本界面的使用控制台输出的测试运行器(test runner),如图1.2所示。

为了运行这个文本界面的测试运行器,在Windows操作系统的C:\junit4.6或UNIX操作系统的/opt/junit4.6下打开一个命令行窗口,并且输入操作系统下相应的命令。

如果是Windows操作系统:

如果是UNIX操作系统:

AllTests类包含了一个main方法来执行测试例子:

图1.1显示了测试执行的结果。图1.1 使用文本界面的测试运行器来执行JUnit发布包附带的测试示例

注意,JUnit文本界面的测试运行器使用小圆点来表示通过测试。如果测试出现错误,则显示字母E,而不是小圆点。

在本书的第3部分,我们会介绍使用Ant构建工具和Maven构建工具来运行测试。1.6 使用JUnit测试

JUnit拥有许多功能,可以使编写、运行测试更加容易。通过本书,你将可以了解到这些功能在实际中的各种运用。● 针对每个单元测试,单独测试类实例和类加载器,以避免副作用。● JUnit注释提供了资源初始化和回收方法:@Before、

@BeforeClass、@After和@AfterClass。● 各种不同的assert方法使得检查测试结果更加简单。● 与各种流行工具(如Ant和Maven)的整合,以及与流行IDE(如

Eclipse、NetBeans、IntelliJ和JBuilder)的整合。

事不宜迟,我们赶紧来看一下代码1.4,看看使用JUnit编写的简单Calculator测试会是什么样子。

代码1.4 使用JUnit编写的CalculatorTest程序

这是一个非常简单的测试,让我们来仔细分析这段代码。在❶部分,我们首先定义了一个测试类。唯一的限制是这个类必须是公有的,我们可以对它任意命名。但通常的做法是在类名称的末尾添加“Test”字样。也要注意,虽然在JUnit 3中我们需要扩展TestCase类,但是在JUnit 4中,我们已经不需要这样做了。[6]

在❷部分,我们通过添加@Test注释,把这个方法标记为一个单元测试方法。最好的做法是按照testXXX模式命名测试方法。因为JUnit没有方法名称的限制,所以你可以根据自己喜好命名你的方法;只要它们拥有了@Test注释,JUnit就会执行它们。

在❸部分,我们通过创建Calculator类的一个实例(被测试的对象)开始进行测试,并且在❹部分,就像前面的操作一样,我们通过调用测试方法并传递两个已知值来执行测试。

在❺部分,JUnit框架开始显现威力了!为了检查测试结果,我们调用了assertEquals方法,这个方法是我们使用这个类的第一行中的静态导入来导入的。assertEquals方法的Javadoc如下所示:

在代码1.4中,我们传递给assertEquals以下参数:

因为传递给calculator的值分别是10和50,然后告诉assertEquals预期的和为60(因为我们相加的是整数,所以delta为0)。当我们调用calculator对象时,我们把返回值传给了一个叫做result的局部变量。因此,我们将这个变量传递给assertEquals,来与预期的值60做比较。

如果实际值不等于预期值,那么JUnit就抛出一个未经检查的异常,这将导致测试失败。

在多数情况下,delta参数可以是零,我们大可放心地忽略它。它总是伴随着非精确计算(其中包括许多浮点计算)而出现。delta提供了一个误差范围。如果实际值在expected - delta和expected + delta范围之内,则测试算通过。当进行带有舍入误差或截断误差的数学运算时,或者当断言一个关于文件修改日期的条件时,你就会发现它非常有用,因为这些数据的精确度取决于操作系统。

假设我们已经把代码1.1和代码1.4中的代码输入到C:\junitbook\ch01-jumpstart目录下(如果是UNIX操作系统则是/opt/junitbook/ch01-jumpstart),那么我们首先通过在那个目录中打开命令行提示并输入以下命令来编译代码(我们假定javac可执行文件在操作系统的PATH中)。

如果是Windows操作系统:

如果是UNIX操作系统:

我们现在通过输入以下命令,准备启动控制台测试运行器。

如果是Windows操作系统:

如果是UNIX操作系统:

图1.2显示了运行结果。

在代码1.4中,关于JUnit的CalculatorTest类,最值得一提的是,其代码要比代码1.2中的第一个CalculatorTest程序更易于编写。此外,我们也可以通过JUnit框架自动运行测试。

当我们在命令行提示下运行测试时(如图1.2所示),我们可以看到运行测试所花费的时间和已通过的测试数量。还有许多其他运行测试的方法,从IDE(如Eclipse)到构建工具(如Ant)。这个简单的例子只是让你初步领略了一下JUnit和单元测试的强大。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载