依赖注入和单元测试

1. 一辆简单的car

首先我们考虑一个简单的例子,这里我们使用engine 类和car 类。为了更加清楚的描述问题,我们将类和接口都置空。每辆car会有一个engine,我们想给car装备上著名的MooseEngine。

Engine类如下:

复制代码

1publicinterfaceEngine{2
3}4
5publicclassSlowEngineimplementsEngine{6
7}8
9publicclassFastEngineimplementsEngine{10
11}12
13publicclassMooseEngineimplementsEngine{14
15}

复制代码

然后我们可以得到一个car类:

1publicclassCar{2
3privateMooseEngineengine;4
5}

这是一辆非常棒的汽车,但是即使有其他种类的引擎上市,我们也不能装备这些引擎了。我们说这里的car类和MooseEngine类是紧耦合的(tightly coupled)。虽然MooseEngine很棒,但是如果我们想把它换成别的引擎呢?

回到顶部

2. 接口编程

你可能已经注意到了MooseEngine实现了Engine接口。其它引擎也实现了同样的接口。我们可以想一想,当我们设计我们的Car类时,我们想让一辆“car”装备一个“engine”。所以我们重新实现一个Car类,这次我们使用Engine接口:

1publicclassCar{2
3privateEngineengine;4
5}

接口编程是依赖注入中的一个很重要的概念。我听到了你的尖叫,“等一下,你在这里使用接口,具现类(concrete class)该怎么办?你在哪里设置(set)引擎?我想在我的汽车中装备MooseEngine”。我们可以按下面的方式来设置它:

1publicclassCar{2
3privateEngineengine=newMooseEngine();4
5}

但这就是有用的么?它看上去和第一个例子没有多大区别。我们的car仍然同MooseEngine是紧耦合的。那么,我们该如何设置(set或者说注入(inject))我们的汽车引擎呢?

回到顶部

3. 依赖注入介绍

就像依赖注入这个名字一样,依赖注入就是注入依赖,或者简单的说,设置不同实例之间的关系。一些人将它同好莱坞的一条规矩关联了起来,“不要给我打掉话,我打给你。”我更喜欢叫它“bugger”法则:“我不关心你是谁,按我说的做。”在我们的第一个例子中,Car依赖的是Engine的具现类MooseEngine。当一个类A依赖于另外一个类B的时候,类B的实现直接在类A中设置,我们说A紧耦合于B。第二个例子中,我们决定使用接口来代替 具现类MooseEngine,这样就使得Car类更加灵活。并且我们决定不去定义engine的具现类实现。换句话说,我们使Car类变为松耦合(loosely coupled)的了。Car不再依赖于任何引擎的具现类了。那么在哪里指定我们需要使用哪个引擎呢?依赖注入该登场了。我们不在Car类中设置具现化的Engine类,而是从外面注入。这又该如何实现呢?

3.1 使用构造函数来注入依赖

设置依赖的一种方法是把依赖类的具体实现传递给构造函数。Car类将会变成下面这个样子:

复制代码

1publicclassCar{2
3privateEngineengine;4
5publicCar(Engineengine){6
7this.engine=engine;8
9}10
11}

复制代码

然后我们就可以用任何种类的engine来创建Car了。例如,一个car使用MooseEngine,另外一个使用crappy SlowEngine:

复制代码

1publicclassTest{2
3publicstaticvoidmain(String[]args){4
5CarmyGreatCar=newCar(newMooseEngine());6
7CarhisCrappyCar=newCar(newSlowEngine());8
9}10
11}

复制代码

3.2 使用setter来注入依赖

另外一种设置依赖的普通方法就使用setter方法。当需要注入很多依赖的时候,建议使用setter方法而不是构造函数。我们的car类将会被实现成下面的样子:

复制代码

1publicclassCar{2
3privateEngineengine;4
5publicvoidsetEngine(Engineengine){6
7this.engine=engine;8
9}10
11}

复制代码

它和基于构造函数的依赖注入非常类似,于是我们可以用下面的方法来实现上面同样的cars:

复制代码

1publicclassTest{2
3publicstaticvoidmain(String[]args){4
5CarmyGreatCar=newCar();6
7myGreatCar.setEngine(newMooseEngine());8
9CarhisCrappyCar=newCar();10
11hisCrappyCar.setEngine(newSlowEngine());12
13}14
15}

复制代码

回到顶部

4. 在单元测试中使用依赖注入

如果你将Car类的第一个例子同使用setter依赖注入的例子进行比较,你可能认为后者使用了额外的步骤来实现Car类的依赖注入。这没错,你必须实现一个setter方法。但是当你在做单元测试的时候,你会感觉到这些额外的工作都是值得的。如果你对单元测试不熟悉,推荐你看一下这个帖子单元测试有毒。我们的Car的例子太简单了,并没有把依赖注入对单元测试的重要性体现的很好。因此我们不再使用这个例子,我们使用前面已经讲述过的关于篝火故事的例子,特别是在在单元测试中使用mock中的部分。我们有一个servlet类,通过使用远端EJB来在农场中”注册”动物:

复制代码

1publicclassFarmServletextendsActionServlet{2
3publicvoiddoAction(ServletDataservletData)throwsException{4
5Stringspecies=servletData.getParameter("species");6
7StringbuildingID=servletData.getParameter("buildingID");8
9if(Str.usable(species)&&Str.usable(buildingID)){10
11FarmEJBRemoteremote=FarmEJBUtil.getHome().create();12
13remote.addAnimal(species,buildingID);14
15}16
17}18
19}

复制代码

你已经注意到了FarmServlet被紧耦合到了FarmEJBRemote实例中,通过调用“FarmEJBUtil.getHome().create()”来取回实例值。这么做会非常难做单元测试。当作单元测试的时候,我们不想使用任何数据库。我们也不想访问EJB服务器。因为这不仅会使单元测试很难进行而且会使其变慢。所以为了能够顺利的为FarmServlet类做单元测试,最好使其变成松耦合的。为了清除FarmServlet和FarmEJBRemote之间的紧依赖关系,我们可以使用基于setter的依赖注入:

复制代码

1publicclassFarmServletextendsActionServlet{2
3privateFarmEJBRemoteremote;4
5publicvoidsetRemote(FarmEJBRemoteremote){6
7this.remote=remote;8
9}
10
11publicvoiddoAction(ServletDataservletData)throwsException{12
13Stringspecies=servletData.getParameter("species");14
15StringbuildingID=servletData.getParameter("buildingID");16
17if(Str.usable(species)&&Str.usable(buildingID)){18
19remote.addAnimal(species,buildingID);20
21}22
23}24
25}

复制代码

在真实的部署包中,我们确保通过调用“FarmEJBUtil.getHome().create()”而创建的一个FarmServlet远端成员实例会被注入。在我们的单元测试中,我们使用一个虚拟的mock类来模拟FarmEJBRemote。换句话说,我们通过使用mock类来实现FarmEJBRemote:

复制代码

1classMockFarmEJBRemoteimplementsFarmEJBRemote{2
3privateStringspecies=null;4
5privateStringbuildingID=null;6
7privateintnbCalls=0;8
9publicvoidaddAnimal(Stringspecies,StringbuildingID)10
11{12
13this.species=species;14
15this.buildingID=buildingID;16
17this.nbCalls++;18
19}20
21publicStringgetSpecies(){22
23returnspecies;24
25}26
27publicStringgetBuildingID(){28
29returnbuildingID;30
31}32
33publicintgetNbCalls(){34
35returnnbCalls;36
37}38
39}40
41
42
43publicclassTestFarmServletextendsTestCase{44
45publicvoidtestAddAnimal()throwsException{46
47//OurmockactinglikeaFarmEJBRemote48
49MockFarmEJBRemotemockRemote=newMockFarmEJBRemote();50
51//Ourservlet.Wesetourmocktoitsremotedependency52
53FarmServletservlet=newFarmServlet();54
55servlet.setRemote(mockRemote);56
57
58
59//justanothermockactinglikeaServletData60
61MockServletDatamockServletData=newMockServletData();
62
63mockServletData.getParameter_returns.put("species","dog");64
65mockServletData.getParameter_returns.put("buildingID","27");66
67
68
69servlet.doAction(mockServletData);70
71assertEquals(1,mockRemote.getNbCalls());72
73assertEquals("dog",mockRemote.getSpecies());74
75assertEquals(27,mockRemote.getBuildingID());76
77}78
79}

复制代码

这样很容易就能测试FarmServlet了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结