zoukankan      html  css  js  c++  java
  • 依赖注入和单元测试

    上一篇博文介绍了测试的相关概念,这篇主要说一下依赖注入以及如何在单元测试中使用。原文链接:

    http://www.javaranch.com/journal/200709/dependency-injection-unit-testing.html 

    近些年来对于依赖注入(Dependency Injection)这个词大家已经应该很熟悉了。我们经常使用它因为这是一个非常好的面向对象概念。你可能也听说过Spring框架(Spring Framework),就是所谓的依赖注入容器,在你的印象里面依赖注入和Spring是等同的。但这个想法是错误的,依赖注入是一个很简单的概念,它可以被应用到任何地方,除了依赖注入容器之外,它同样能够被应用到单元测试中。这篇文章我们讨论一下几点:

    • 什么是依赖注入
    • 如何实现一个友好的依赖注入类
    • 为什么依赖注入可以使单元测试更加简单

    Ladies and gentlemen,开动你的引擎!

    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 FarmEJBRemote
    48 
    49                MockFarmEJBRemote mockRemote = new MockFarmEJBRemote();
    50 
    51                // Our servlet. We set our mock to its remote dependency
    52 
    53                FarmServlet servlet = new FarmServlet();
    54 
    55                servlet.setRemote(mockRemote);
    56 
    57            
    58 
    59                // just another mock acting like a ServletData
    60 
    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了。

    5. 总结

    • 使用接口而非具现类来表现依赖。
    • 避免在类中隐式的设置(set)一个依赖的具体实现
    • 依赖的具体实现有很多种方法,包括基于构造函数的依赖注入和基于setter的依赖注入
    • 依赖注入使单元测试变的非常灵活。
  • 相关阅读:
    输入框input只能输入数字和小数点
    ES6判断数组是否存在重复元素
    AutoPostBack的含义
    首次的boe with wss
    我的msn能在w2k3上使用了哈哈
    挑战excel
    wss的webpart的3种开发方式
    我来操作dts olap
    My dbconn of ASP
    sharepoint学习理解过程
  • 原文地址:https://www.cnblogs.com/harlanc/p/6876462.html
Copyright © 2011-2022 走看看