2022年测试SpringMVC应用程序 .pdf
《2022年测试SpringMVC应用程序 .pdf》由会员分享,可在线阅读,更多相关《2022年测试SpringMVC应用程序 .pdf(13页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。
1、第 10 章 测试 Spring MVC 应用程序编写模块化、可插拔以及松散解耦的应用程序也就是在创建极易测试的应用程序。Spring 框架鼓励采用创建快速、简单、有用的单元测试和集成测试的方法来构建应用程序。本章中,我们将看到为Spring MVC 应用程序组件编写测试的策略和技术。我们将在 JUnit 测试框架之上,使用Spring 内建的测试存根(在spring-mock.jar中),同时介绍用于集成测试的模拟对象(通过jMock 库)。用 Spring 的方式构建应用程序的一个主要卖点是使创建测试变得可行,本章将介绍如何进行操作。10.1 概览测试(testing)的准确意思到底是什么
2、呢?提到测试,我们特指单元测试和集成测试。这些测试分别关注于类的实际方法和软件组件之间的交互。我们没有涵盖用户验收测试,这种测试关注于用户与应用程序界面的交互。我们当然不是在贬低用户验收测试的作用,不过单元测试和集成测试应该使大部分问题到达用户之前就被发现。10.1.1 单元测试已经有大量关于单元测试的文献,在此不再赘述了。但无论如何,我们还将花时间去讨论什么是单元测试(以及什么不是)并将其与集成测试相比较。提示在寻找更多关于单元测试的资料吗?我们推荐J.B. Rainsberger和 Scott Stirling合著的 JUnit Recipes(Manning Publications,
3、2004)以及 Andrew Hunt 和 David Thomas合著的 Pragmatic Unit Testing in Java(The Pragmatic Programmer, 2003)。单元测试的基本定义是“在一个独立的测试环境中检查一个独立软件模块的正确性”。虽然我们相信这个陈述是正确的,但或许有更好的方式定义单元测试。一个测试必须遵循以下这些原则才能真正称为单元测试。ql 快速运行:单元测试必须相当快速地运行。如果它需要等待数据库链接或者外部服务器进程或者解析大文件,其用途很快会变得很有限。一个测试需要提供快速的响应和即时的回报。ql 零外部配置: 单元测试一定不能要求任何
4、外部配置文件,连简单的文本文件都不行。测试配置必须由测试框架本身通过调用代码来提供和设置。这个目标是减少测试的运行时间和消除对外部的依赖(外部文件会随时间而改变,导致测试变得不同步)。测试用例条件在测试框架中应该是明确的,尽量创建更多可读的测试条件。ql 运行独立于其他测试的测试:单元测试必须能在完全独立的情况下运行。换言之,单元测试不能依赖其前后的某个别的测试运行。每个测试都是一个独立的单元。ql 零外部资源: 单元测试必须不依赖于任何外面的资源,如数据库链接或者Web 服务,这些资源不仅会降低测试速度,而且它们在测试的控制之外,因而无法保证测试在一个正确的状态中运行。ql 保持外部状态不改
5、变:单元测试一定不能留下曾经运行过的痕迹。单元测试本来就应该是可以重复写的,所以它们必须在运行之后自行清理痕迹。显然,当测试不依赖外部资源的时候这是很简单的(这些通常非常难以清除或者恢复)。ql 测试最小单元代码:单元测试必须尽可能测试最小单元代码以保证在测试时孤立出代码。在面向对象编程中,这样的单元通常是一个对象或者类的方法。这样写单元测试是为了将一个方法独立于其他方法测试,从而减少了可能包含潜在 bug 的代码行数。注意显然, Spring 应用程序运行时严重依赖于ApplicationContext及其 XML配置文件。但是,你的单元测试不需要依赖这些。即使不是所有的Spring 实践,
6、也有很多的实践提倡脱离Spring 本身运行代码。你的代码应当总能脱离 ApplicationContext来进行单元测试。要测试系统的结合和配置,参见本章稍后的“集成测试”。1工具名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 13 页 - - - - - - - - - 在本书中的所有单元测试,我们都使用了普通的JUnit (http:/www.junit.org)库。在 Java 中这是写单元测试的事实标准,有广泛的行业支持以及许多插件。Spring 框架使用 JU
7、nit 来写它的测试,所以如果你想看看单元测试是怎么创建的,那么这儿有许多很好的例子。你可以在下载的发布版本的test目录中找到框架的测试。2实例我们将快速地展示一个单元测试的例子,以提供一些观点并且给我们的讨论建立一些基础。第 4 章中的 Flight类是单元测试很好的候选,因为它不需要额外的资源且包含业务逻辑陈述。代码清单 10-3 是 Flight类完整的测试用例。我们通过setUp() 开始创建和初始化一个Flight实例,完成在两个城市之间的一个FlightLeg 。有一个公用的配置好的Flight实例使每个测试能够简单地编写,因为测试方法间数据一致。正如你看到的, 我们喜欢把每个测
8、试需要的环境放入各自的测试方法中。这种技术有别于你在发布的Spring 源代码里面找到的许多测试,一般会在那里发现,一个测试方法包含了许多不同的动作和断言。我们相信测试需要独立运行,所以为了更显式地分离测试而牺牲简洁。这种分离测试方法类型的另外一个好处是降低了测试时正交代码影响真正被测试方法的风险。一些测试是非常简单的,测试仅仅是取得或者只读访问方法。例如,代码清单10-1 是检验当 Flight实例创建时的费用恰好与 getTotalCost()返回的全部费用相等。这些看起来简单的方法与表面上非常复杂的方法一样需要测试。这不仅提高了测试覆盖率,而且一旦重构开始时(迟早会发生)它就提供了一个非
9、常有用的安全网。代码清单 10-1 testGetTotalCost()方法其他测试方法,如代码清单10-2 所展示的那个,需要比setUp() 方法提供更多的配置。我们测试本地参数以使测试能有一个准确的结果,正如一个连续的有正确开始和结束时间的FlightLeg 。注意, assertEquals()方法计算期望值独立于测试开始时设置的start和 end 对象。为什么不仅仅使用assertEquals(end.getTime()-start.getTime(), flight.getTotalTravelTime()呢?期望值( assertEquals()方法的第 1 个参数)决不应该通
10、过对参与测试的参数的计算而得出,以便确保测试本身没有修改任何影响其正确性的数据。getTotalTravelTime()可能改变 start和 end对象的值,但是这种可能性不大。如果它确实成功地改变了这些值,那么即使写了这个测试方法而没有返回期望的值,测试仍可能显示成功。换句话说,你将得到一个错误的肯定。代码清单 10-2 testGetTotalCost()方法把它们集合一起,代码清单10-3 包含完整的FlightTest。代码清单 10-3 Flight类的单元测试 私有方法正如你注意到的,我们没有写任何明确测试类的私有方法的测试。的确,私有方法是内部的细节实现方法,它不是类公开 AP
11、I 的一部分。写测试是为了从类客户的观点测试对象怎么运转。这是单元测试的一个关键点,因为它们强制测试作者(应该就是创建这个类的人)像客户一样思考。测试作者很快开始提问,我应该怎么使用这个类?要做好工作,我从这个类中需要得到点什么?写单元测试可以快速暴露该类在可用性上的任何缺点,这些是非常有用的。3我如何得知测试何时做完名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 13 页 - - - - - - - - - 写单元测试时的一个常见问题是“我如何知道自己是否写了足够多的测
12、试?”这不是一个能快速回答的问题,但是一些准则和工具可以帮助你确定实际测试的代码有多少。第一准则:用你期望之外的值测试该方法。首先,使用零和null作为输入值测试你的方法。你的方法应该适当地处理这种案例,我们称其为边缘案例(edge case)。边缘案例是可能暴露问题的条件,因为它们接近或者在可接受值的边缘,例如零、 null 、无穷大以及根据业务逻辑定义的最大值或者最小值条件。为所有这些边缘案例创建测试是通向完整性的好开端。一旦写完了边缘案例测试,接下来只要为普通案例编写测试。这些测试环境需要使用典型的出现非常频繁的值。在此不需要测试每个单一的值(大量真实的数字已存在),所以在这里请使用你的
13、最佳判断。边缘案例对测试显然更重要,因为它们时常被代码忘记或者忽略,但是普通案例也总是需要被测试的。处理好这些普通案例之后,看一下你要测试的算法,并且鉴别所有的分支和决策点。为了完成一组完整的测试,每个分支和指向分支的条件必须得以测试。例如,有一个switch语句,确保测试使用的值能演练每一种情况,包括default语句。这同样适用于任何一个异常可能被抛出的地方 见例子 FlightTest(代码清单 10-3)的testWrongEndTime()。当然,除了最简单的软件外,任何软件都有许多分支、异常案例和执行路径。为确保你的测试已经演练了所有潜在的条件,可以使用特定工具追踪哪些代码已经被测
14、试而哪些没有。这被称为代码覆盖率工具,它们与测试框架一起(尽管它们并不需要这些)告知这个系统已经有多少代码测试过。代码覆盖率工具,比如Clover (http:/ EMMA(http:/emma. 该工具会产生关于源代码的哪些行被单元测试训练的详细报告。该报告如图 10-1 所示,它提供了一个完美的可视化方法,查看系统中有多少部分通过你的单元和集成真正得到测试。图 10-1 来自 Clover 的 HTML 报告提示 Spring使用 Clover 做代码覆盖率,可以通过Ant 构建源代码生成和查看最新的覆盖率报告。即使已经在测试中成功地达到100% 的代码覆盖率,还不一定已完成编写测试。代码
15、覆盖率工具是非常有用的,但是它们不能指出你是否充分覆盖率边缘案例。例如,它们可能告诉你是否测试过一个假设的addition()方法,但是它们不知道你是否尝试null 、零、负数或者无穷大。你应该使用代码覆盖率加边缘案例测试的组合来真正开始感觉自己拥有一个完整的测试集。4什么时候写测试编写测试时另外一个常见问题是“在构建系统进程中,什么时候创建测试?”这个问题的答案依赖于你赞成用什么软件开发方法。极限编程( http:/www.extremeprogramming.org)即 XP是最早的敏捷方法(http:/agilem- anifesto.org/)之一。XP的一种主要原则是名为测试驱动开发
16、的技术即TDD 。就是说在任何代码创建之前必须先写单元测试。在最基础级别,这意味着如果严格遵循这点那么所有代码都将有相应的测试。然而,如果TDD的支持者是正确的,这种技术则有着更广泛的含义。ql 如果先创建测试,那么需要一直对系统进行测试。当紧要关头来临时,单元测试通常被丢在一边。这是个危险的决定,尤其是在开发需要加快的紧要关头(从而是非常危险的)。ql 需要得到对所有代码添加和改变的立即反馈。当软件成长和重构时变得非常重要时,保险的单元测试网是基本的。通过先编写测试,你将立即知道重构是否起到了作用以及衰退bug 是否被引入。名师资料总结 - - -精品资料欢迎下载 - - - - - - -
17、 - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 13 页 - - - - - - - - - ql 系统设计将会从可能的最简单形式中形成。TDD支持者认为由于代码为响应测试而创建,系统只能引入让系统通过测试的代码。通过关注测试的通过,代码没有机会扩张到非最初预定的或非必需的区域。Rational Unified Process (RUP )仅仅定义了一个建造(construction)阶段,代码和测试在那里没有以指定的顺序创建。特征驱动开发( FDD )声明了单元测试发生在第五个过程,“根据特征构建”。FDD 没有明确定义在该过程中什么时
18、候创建单元测试,只是说它们一定会被创建。在这个主题上,我们的个人感觉是这样的:尽可能早地测试。尽管严格遵循TDD有一点教条,但先编写测试真的会帮助产生更简单的设计(当然,它帮助你知道要怎么走)。在软件构建期间编写测试(尤其在测试的模块创建或者更新时)是一个极好的主意。我们已经注意到由于对系统的信心以及测试本身的健壮性,开始时任何编码速度的降低都是值得的。底线是不要延迟编写这些单元测试。值得注意的是尽可能早地编写测试的建议是针对单元测试的。集成测试自然在之后的过程中创建,因为那时系统的大部分已经构建好了。5代码有依赖的时候会怎么样单元测试建议,例如当测试领域对象模型时,测试最小数量的代码是非常合
19、理的,因为易于创建和控制依赖实例。正如代码清单10-3,FlightTest简单地创建一个新的对象如FlightLeg以创建特定的测试条件。但是,如果代码需要特别重或者额外的资源的依赖,编写单元测试将变得很困难。例如,考虑怎么为下面的服务层类写一个单元测试。如代码清单10-4 所示,我们已经创建了一个简单的AccountService接口,实现为 AccountServiceImpl。服务类委托给一个AccountDao 以从持久层载入Account 实例。Account 类为它的激活实现了业务逻辑。代码清单 10-4 AccountServiceImpl 我们可能已经看到一些单元测试必须检测
20、的条件,包含:ql 如果数据访问对象(DAO )返回 null账号,会怎么样?ql 如果方法参数是null ,会怎么样?ql 怎么确保通过DAO 返回的账号是激活的?为了成功测试这些条件,服务对象需要一个可运行的DAO 。但是,为了避免违反我们的单元测试规则,一定不能与数据库进行真正的交互。因此,AccountServiceImpl使用的 AccountDao 实例不能是真实的实现。我们需要一个看起来及感觉起来都像真实的对象来代替AccountDao,但不是从数据库取出对象而是从内存中返回对象。为了解决这个问题,如DAO 层的依赖可以用模拟对象代替。我们不使用真实的AccountDao,而是创
21、建一个模拟的AccountDao 替代物,在测试运行之前将其设置到AccountServiceImpl对象中。这样我们将完全控制所有依赖,将服务对象孤立为测试中的变量,同时也避免与额外资源的任何交互。注解依赖注入在允许通过模拟对象简单测试中扮演了重要的角色。因为依赖经由简单的设置方法设置到类中,所以测试时该依赖可以简单地用一个模拟对象代替。6模拟对象模拟对象( mo ck object)将智能和可编程能力添加到存根(stub )类中,使它们在本质上成为脚本化的存根。将到模拟对象看作依赖的巧妙的占位符。模拟对象是在利用它们的测试中动态创建的。它们一般使用JDK的java.lang.reflect
22、.Proxy对象来实现,当替换该行为时实现依赖的接口。很多高级的模拟对象库同样支持为没有实现接口的类创建模拟。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 13 页 - - - - - - - - - 模拟对象允许记录模拟的行为以便为测试创建不同的环境。例如,你可以控制一个方法调用返回哪个对象或者指示模拟对象抛出一个异常而非返回一个值。该技术允许从本质上注入对象到作为方法调用结果的测试中。模拟对象同样让你精确指定在模拟中哪个方法需要被调用以及以什么顺序调用。这是一种in
23、side-out类型的测试,因为可以确保在一个依赖上只有期望的方法被调用。一旦配置,如果一个期望的方法没有被调用或者在模拟中调用的方法不是期望的,可以指示模拟对象使测试失败。这是 3 个主要的模拟对象库,每个都有自己的优点和缺点。参见表10-1. 表 10-1 模拟对象库名称 URL 文档备注MockObjects http:/最小,不足起初的模拟对象库之一jMock http:/www.jmock.org好,包括开始向导和指南 MockObjects的改写,处理模拟接口和类EasyMock http:/www.easymock.org好,许多示例由 Spring 用于测试按照模拟对象需求,我
24、们推荐jMock 或者 EasyMock。如表 10-1 中的备注所示, Spring 使用 EasyMock做测试,那么可能由于在 Spring 源代码中的例子的数量选择这个包。然而,我们拥有很多jMock 的经验,所以在本章中将以之为例。选好了模拟对象库,该是为AccountServiceImpl编写单元测试的时候了。q 模拟对象技术在测试时我们使用模拟对象有两个主要原因:独立测试类免受外部影响以及控制依赖。当单元测试需要孤立地测试一个单元的代码时,很难以某种形式找到一个完全无效的依赖的类或方法。如果该依赖是一个简单的POJO ,使用 new操作是一个创建仅仅用于测试的实例的简单方法。然而
25、,如果该依赖不是容易创建或者需要重量级的资源如数据链接,使用new操作通常是不可能的。模拟对象介入“模拟”依赖,因而在测试中允许代码不调用外部资源操作。真正的模拟对象,不仅仅是简单的存根,它允许控制其行为。当模拟重量级资源如DAO时这是极其重要的。因为你依靠一个模拟DAO 测试,没有数据库,那么DAO如何得到对象呢?模拟不仅仅可以被告知怎么看(接口的样子),名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 13 页 - - - - - - - - - 而且可以被告知怎么做。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 2022年测试SpringMVC应用程序 2022 测试 SpringMVC 应用程序
限制150内