依赖注入和单元测试

前端之家收集整理的这篇文章主要介绍了依赖注入和单元测试前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

1. 一辆简单的car

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

Engine类如下:

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

  1. 1publicclassCar{2
  2. 3privateMooseEngineengine;4
  3. 5}

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

回到顶部

2. 接口编程

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

  1. 1publicclassCar{2
  2. 3privateEngineengine;4
  3. 5}

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

  1. 1publicclassCar{2
  2. 3privateEngineengine=newMooseEngine();4
  3. 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类将会变成下面这个样子:

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

3.2 使用setter来注入依赖

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

回到顶部

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

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

复制<a href=代码" style="margin:0px;float:left;border:none;" src="http://common.cnblogs.com/images/copycode.gif">

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

猜你在找的设计模式相关文章