依赖注入和单元测试

1. 一辆简单的car

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

Engine类如下:

 1 public interface Engine { 2 
 3 } 4 
 5 public class SlowEngine implements Engine { 6 
 7 } 8 
 9 public class FastEngine implements Engine {10 
11 }12 
13 public class MooseEngine implements Engine {14 
15 }

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

1 public class Car {2 
3        private MooseEngine engine;4 
5 }

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

回到顶部

2. 接口编程

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

1 public class Car {2 
3         private Engine engine;4 
5 }

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

1 public class Car {2 
3         private Engine engine = new MooseEngine();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类将会变成下面这个样子:

 1 public class Car { 2 
 3         private Engine engine; 4 
 5         public Car(Engine engine) { 6 
 7                this.engine = engine; 8 
 9         }10 
11 }

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

 1 public class Test { 2 
 3         public static void main(String[] args) { 4 
 5                Car myGreatCar = new Car(new MooseEngine()); 6 
 7                Car hisCrappyCar = new Car(new SlowEngine()); 8 
 9         }10 
11 }

3.2 使用setter来注入依赖

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

 1 public class Car { 2 
 3         private Engine engine; 4 
 5         public void setEngine(Engine engine) { 6 
 7                this.engine = engine; 8 
 9         }10 
11 }

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

 1 public class Test { 2 
 3         public static void main(String[] args) { 4 
 5                Car myGreatCar = new Car(); 6 
 7                myGreatCar.setEngine(new MooseEngine()); 8 
 9                Car hisCrappyCar = new Car();10 
11                hisCrappyCar.setEngine(new SlowEngine());12 
13         }14 
15 }

回到顶部

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

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

 1 public class FarmServlet extends ActionServlet { 2 
 3         public void doAction( ServletData servletData ) throws Exception { 4 
 5                String species = servletData.getParameter("species"); 6 
 7                String buildingID = servletData.getParameter("buildingID"); 8 
 9                if ( Str.usable( species ) && Str.usable( buildingID ) ) {10 
11                        FarmEJBRemote remote = FarmEJBUtil.getHome().create();12 
13                        remote.addAnimal( species , buildingID );14 
15                }16 
17         }18 
19 }

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

 1 public class FarmServlet extends ActionServlet { 2 
 3         private FarmEJBRemote remote; 4 
 5         public void setRemote(FarmEJBRemote remote) { 6 
 7                this.remote = remote; 8 
 9         }  
10 
11         public void doAction( ServletData servletData ) throws Exception {12 
13                String species = servletData.getParameter("species");14 
15                String buildingID = servletData.getParameter("buildingID");16 
17                if ( Str.usable( species ) && Str.usable( buildingID ) ) {18 
19                        remote.addAnimal( species , buildingID );20 
21                }22 
23         }24 
25 }

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

 1 class MockFarmEJBRemote implements FarmEJBRemote { 2 
 3         private String species = null; 4 
 5         private String buildingID = null; 6 
 7         private int nbCalls = 0; 8 
 9         public void addAnimal( String species , String buildingID )10 
11         {12 
13                this.species = species ;14 
15                this.buildingID = buildingID ;16 
17                this.nbCalls++;18 
19         }20 
21         public String getSpecies() {22 
23                return species;24 
25         }26 
27         public String getBuildingID() {28 
29                return buildingID;30 
31         }32 
33         public int getNbCalls() {34 
35                return nbCalls;36 
37         }38 
39 }40 
41  
42 
43 public class TestFarmServlet extends TestCase {44 
45         public void testAddAnimal() throws Exception {46 
47                // Our mock acting like a FarmEJBRemote48 
49                MockFarmEJBRemote mockRemote = new MockFarmEJBRemote();50 
51                // Our servlet. We set our mock to its remote dependency52 
53                FarmServlet servlet = new FarmServlet();54 
55                servlet.setRemote(mockRemote);56 
57            
58 
59                // just another mock acting like a ServletData60 
61                MockServletData mockServletData = new MockServletData(); 
62 
63                mockServletData.getParameter_returns.put("species","dog");64 
65                mockServletData.getParameter_returns.put("buildingID","27");66 
67  
68 
69                servlet.doAction( mockServletData );70 
71                assertEquals( 1 , mockRemote.getNbCalls() );72 
73                assertEquals( "dog" , mockRemote.getSpecies() );74 
75                assertEquals( 27 , mockRemote.getBuildingID() );76 
77         }78 
79 }

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

时间: 2024-08-27 19:26:29

依赖注入和单元测试的相关文章

SpringBug记录 -- java.lang.NullPointerException在Spring单元测试中遇到的空指针异常及依赖注入异常总结

在进行SSM整合过程中遇到了空指针异常以及依赖注入异常,分别说一下. 1.空指针异常: 在进行单元测试时,发现在Controller层出现空指针异常.然后单独测试Service层,发现空指针异常到了Service层.后来发现,原来我在Test方法中New了一个新的Service.导致空指针异常,由于Ioc机制,Spring已经自动帮我们注入了这个变量,所以重新声明的话会导致空指针异常(果然基础还是不够扎实哈哈,长记性了) 2.依赖注入失败异常. 在启动tomcat时,发现Tomcat报异常,无法

2.1 依赖注入

依赖注入 什么是依赖注入 传统方式的问题 解决方案 构造函数注入模式 属性注入模式 依赖注入框架 ABP依赖注入基础设施 注册依赖注入 传统注册 帮助接口 自定义/直接注册 使用IocManager 分析 构造函数和属性注入 IIocResolver,IIocManager和IScopedIocResolver 附加的 IShouldInitialize接口 ASP.NET MVC和ASP.NET Web API集成 ASP.NET Core 集成 最后建议 什么是依赖注入? 如果你已经知道依赖

ASP.NET Web API中的依赖注入

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 什么是依赖注入 依赖,就是一个对象需要的另一个对象,比如说,这是我们通常定义的一个用来处理数据访问的存储,让我们用一个例子来解释,首先,定义一个领域模型如下: namespace Pattern.DI.MVC.Models{ public cl

[Architect] ABP(现代ASP.NET样板开发框架)(7) 依赖注入

本节目录: 什么是依赖 传统方式的问题 解决方案 构造函数注入 属性注入 注入框架 Abp依赖注入框架 注册 通常注册 帮助接口 自定义注册 解析 构造函数 & 属性注入 IIocResolver & IIocManager 扩展 IShouldInitialize ASP.NET MVC & ASP.NET Web API注入 什么是依赖 如果你已经知道依赖注入思想,构造函数和属性注入模式,你可以跳到下节. 维基:"依赖注入是1种软件设计模式,在这种模式下,1个或多个依

控制反转(IoC)与依赖注入(DI)

前言 最近在学习Spring框架,它的核心就是IoC容器.要掌握Spring框架,就必须要理解控制反转的思想以及依赖注入的实现方式.下面,我们将围绕下面几个问题来探讨控制反转与依赖注入的关系以及在Spring中如何应用. 什么是控制反转? 什么是依赖注入? 它们之间有什么关系? 如何在Spring框架中应用依赖注入? 什么是控制反转 在讨论控制反转之前,我们先来看看软件系统中耦合的对象.图1:软件系统中耦合的对象从图中可以看到,软件中的对象就像齿轮一样,协同工作,但是互相耦合,一个零件不能正常工

ABP依赖注入

ABP依赖注入 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate 本文由 上海-半冷 提供翻译 什么是依赖注入 如果你已经知道依赖注入的概念,构造函

基于DDD的现代ASP.NET开发框架--ABP系列之6、ABP依赖注入

点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate 本文由 上海-半冷 提供翻译 什么是依赖注入 如果你已经知道依赖注入的概念,构造函数和属性注入模式

[译]12-spring依赖注入

每个java应用程序都是由多个类协作才最终生成了终端用户所使用的系统.当编写复杂java应用程序的时,类之间应尽 可能保持独立,因为这样更容易做到代码的重用,也有利于单元测试的开展.spring的依赖注入功能能在保持类相互独立 的同时把他们"粘合"起来. 考虑如下场景:你的应用程序中有个文本编辑器组件,你现在想给你的文本编辑器添加拼写检查的功能.那么你可能写 出如下的代码来: public class TextEditor { private SpellChecker spellChe

007.ASP.NET MVC控制器依赖注入

原文链接:http://www.codeproject.com/Articles/560798/ASP-NET-MVC-Controller-Dependency-Injection-for-Be 前言:在这篇文章中,我将通过一个demo,直截了当地说明依赖注入在MVC框架中的使用. 内容列表: 1.介绍 2.为什么使用控制器依赖注入 3.控制器静态结构 4.自定义控制器 5.Framework中控制器的创建 6.为什么使用控制器工厂模式 7.控制器工厂模式 7.1.目标1 7.2.目标2 8.