依赖注入和单元测试

简介: 上一篇博文介绍了测试的相关概念,这篇主要说一下依赖注入以及如何在单元测试中使用。原文链接: http://www.javaranch.com/journal/200709/dependency-injection-unit-testing.html  近些年来对于依赖注入(Dependency Injection)这个词大家已经应该很熟悉了。

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

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的依赖注入
  • 依赖注入使单元测试变的非常灵活。

 


作者: HarlanC

博客地址: http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

目录
相关文章
|
7月前
|
JavaScript Java 测试技术
单元测试使用依赖注入
单元测试使用依赖注入
115 2
|
自然语言处理 JavaScript 前端开发
JS中的依赖注入 — 在测试中未使用过的最佳工具
让我来为大家介绍在测试中最好的朋友。
JS中的依赖注入 — 在测试中未使用过的最佳工具
|
Java 测试技术 Spring
spring依赖注入单元测试:expected single matching bean but found 2
异常信息:org.springframework.beans.factory.UnsatisfiedDependencyException: Caused by: org.springframework.
1592 0
|
4月前
|
Java 测试技术 开发者
在软件开发中,测试至关重要,尤以单元测试和集成测试为然
在软件开发中,测试至关重要,尤以单元测试和集成测试为然。单元测试聚焦于Java中的类或方法等最小单元,确保其独立功能正确无误,及早发现问题。集成测试则着眼于模块间的交互,验证整体协作效能。为实现高效测试,需编写可测性强的代码,并选用JUnit等合适框架。同时,合理规划测试场景与利用Spring等工具也必不可少。遵循最佳实践,可提升测试质量,保障Java应用稳健前行。
54 1
|
1月前
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
4月前
|
JSON Dubbo 测试技术
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
59 2
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
|
3月前
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
【9月更文挑战第3天】随着软件行业的迅速发展,代码质量和开发效率变得至关重要。本文探讨了Python在自动化及单元测试中的应用,介绍了Selenium、Appium、pytest等自动化测试框架,以及Python标准库中的unittest单元测试框架。通过详细阐述各框架的特点与使用方法,本文旨在帮助开发者掌握编写高效测试用例的技巧,提升代码质量与开发效率。同时,文章还提出了制定测试计划、持续集成与测试等实践建议,助力项目成功。
92 5
|
4月前
|
JSON 测试技术 数据格式
单元测试问题之使用JCode5插件生成测试类如何解决
单元测试问题之使用JCode5插件生成测试类如何解决
163 3