Spring测试 其实很简单

 更新时间:2016年07月08日 16:41:07   作者:Sandbox Wang  
这篇文章主要为大家详细介绍了Spring测试,其实很简单,揭开集成测试神秘的面纱,感兴趣的小伙伴们可以参考一下

在过去的职业生涯里,我经常发现有些人不写测试代码,而他们声称不写的理由是无法轻易地写出覆盖多个不同模块的测试用例。好吧,我相信他们中的大部分要么是缺乏一些比较易掌握的技术手段,要么就是没时间来把它搞清楚,毕竟工作中总会有进度之类的各种压力。因为不知道该如何测试,所以就经常忽略集成测试,由此带来的问题就是越来越糟糕的软件、越来越多的BUG和更加失望的客户。所以我想分享一些个人的经验,揭开集成测试神秘的面纱。 

如何对基于Spring的工程更好地进行集成测试
使用工具: Spring, JUnit, Mockito
想象有这样一个Spring工程,它集成了一些外部服务,例如,一些银行的web服务。那么,为这个工程写测试用例以及在持续集成系统中完成这些测试时所遇到的问题基本都差不多:
 1.每次测试都会有交易进行,每次交易都需要付出金钱成本,这些成本最终由客户承担;
 2.测试时发出的过多的请求有可能被认为是恶意请求,可能造成在银行的账户被封,后果是测试失败;
 3.当使用非生产环境进行测试时,测试结果并不十分可靠,同样,后果是测试失败。
通常情况下,你对单个类进行测试的时候,问题很容易解决,因为你可以虚拟一些外部服务来供调用。但是当对整个巨大的业务流程进行测试的时候,意味你需要对多个部件进行测试,这时,需要你将这些部件都纳入到Spring容器中进行管理。所幸,Spring包含了非常优秀的测试框架,允许你将来自生产环境配置文件中的bean注入到测试环境中,但是对那些被调用的外部服务,需要我们自己去写模拟实现。一般人第一反应可能是在测试的setUp阶段对由Spring注入的bean进行重新注入(修改),但是这种方法需要再仔细考虑一下。 

警告:通过这种方式,你的测试代码打破了容器自身的行为,所以没法保证在真实的环境中也如你测试的结果一样。
事实上,我们无需先实现模拟类然后再把它重新注入到所需的bean中,我们可以让Spring帮助我们一开始就注入模拟类。让我们用代码演示一下。
示例工程包含一个名为BankService的类,代表调用的外部服务,一个名为UserBalanceService的类,它会调用BankService。UserBalanceService实现的非常简单,仅仅完成将余额从String向Double类型的转换。
BankService.java的源码:

 public interface BankService {
 String getBalanceByEmail(String email);
} 

BankServiceImpl.java的源码:

 public class BankServiceImpl implements BankService {
 @Override
 public String getBalanceByEmail(String email) {
  throw new UnsupportedOperationException("Operation failed due to external exception");
 }
} 

UserBalanceService.java的源码:

 interface UserBalanceService {
 Double getAccountBalance(String email);
} 

UserBalanceServiceImpl.java的源码:

 public class UserBalanceServiceImpl implements UserBalanceService {
 @Autowired
 private BankService bankService;
 @Override
 public Double getAccountBalance(String email) {
  return Double.valueOf(bankService.getBalanceByEmail(email));
 }
} 

然后是Spring的XML配置文件,添加所需要的bean声明。
applicationContext.xml的源代码:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="bankService" class="ua.eshepelyuk.blog.springtest.springockito.BankServiceImpl"/>
 <bean id="userBalanceService" class="ua.eshepelyuk.blog.springtest.springockito.UserBalanceServiceImpl"/>
</beans> 

下面是测试类UserBalanceServiceImplTest.java的源代码:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/springtest/springockito/applicationContext.xml")
public class UserBalanceServiceImplProfileTest {
 @Autowired
 private UserBalanceService userBalanceService;
 @Autowired
 private BankService bankService;
 @Test
 public void shouldReturnMockedBalance() {
  Double balance = userBalanceService.getAccountBalance("user@bank.com");
  assertEquals(balance, Double.valueOf(123.45D));
 }
} 

如我们预料的一样,测试方法报UnsupportedOperationException异常。我们现在的目的是把BankService换成我们的模拟实现。直接使用Mockito来生成factory bean的方法是没问题的,但是有更好的选择,使用Springockito框架。继续之前可以先大概了解一下。 

剩下的问题就简单了:如何让Spring注入模拟的bean而不是真实的bean,在Spring 3.1版之前除了新建一个XML配置文件之外没有其他的方法。但是自从Spring引入了bean的profile定义之后,我们有了更加优雅的解决方式,虽然这种方式也需要一个额外的专门用作测试的XML配置文件。下面是这个用来测试的配置文件testApplicationContext.xml的代码:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mockito="http://www.mockito.org/spring/mockito"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.mockito.org/spring/mockito https://bitbucket.org/kubek2k/springockito/raw/tip/springockito/src/main/resources/spring/mockito.xsd">
 <import resource="classpath:/springtest/springockito/applicationContext.xml"/>
 <beans profile="springTest">
  <mockito:mock id="bankService" class="ua.eshepelyuk.blog.springtest.springockito.BankService"/>
 </beans>
</beans> 

做相应修改过之后的测试类UserBalanceServiceImplProfileTest.java的源代码:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/springtest/springockito/testApplicationContext.xml")
@ActiveProfiles(profiles = {"springTest"})
public class UserBalanceServiceImplProfileTest {
 @Autowired
 private UserBalanceService userBalanceService;
 @Autowired
 private BankService bankService;
 @Before
 public void setUp() throws Exception {
  Mockito.when(bankService.getBalanceByEmail("user@bank.com")).thenReturn(String.valueOf(123.45D));
 }
 @Test
 public void shouldReturnMockedBalance() {
  Double balance = userBalanceService.getAccountBalance("user@bank.com");
  assertEquals(balance, Double.valueOf(123.45D));
 }
} 

你可能注意到了,在setUp方法里,我们定义了模拟的行为,并且在类上面加了@Profile的注解。这个注解激活了名为springTest的profile,因此使用Springockito模拟的bean就可以自动注入到任何它所需要的地方了。这个测试的运行结果会成功,因为Spring注入了Springockito 所模拟的版本,而不是applicationContext.xml里所声明的版本。

继续优化我们的测试
 如果我们能将解决这个问题的方法更加推进一步的话,这篇文章看起来才没有缺憾。Springockito提供了另外一个名字叫作
 Springockito Annotation的框架,它允许我们在测试类中使用注解来注入模拟类。继续看下去之前,您最好先去网站上大概瞧瞧。好了,下面是经过修改后的测试代码。

 UserBalanceServiceImplAnnotationTest.java的源代码:
 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = SpringockitoContextLoader.class,
 locations = "classpath:/springtest/springockito/applicationContext.xml")
public class UserBalanceServiceImplAnnotationTest {
 @Autowired
 private UserBalanceService userBalanceService;
 @Autowired
 @ReplaceWithMock
 private BankService bankService;
 @Before
 public void setUp() throws Exception {
  Mockito.when(bankService.getBalanceByEmail("user@bank.com")).thenReturn(String.valueOf(valueOf(123.45D)));
 }
 @Test
 public void shouldReturnMockedBalance() {
  Double balance = userBalanceService.getAccountBalance("user@bank.com");
  assertEquals(balance, valueOf(123.45D));
 }
} 

请注意,这里并没有新引入的XML配置文件,而是直接使用了正式环境的applicationContext.xml。我们使用@ReplaceWithMock这个注解标记了类型为BankService的bean,而后在setUp方法中对模拟类的行为进行了定义。

后记
Springockito-annotations项目有个巨大的优点,那就是,它使我们的测试代码建立在依赖覆盖的基础之上,通过这样,我们既不需要定义额外的XML配置文件,也不需要为了测试而去改动生产环境的配置文件。如果不使用Springockito-annotations的话,我们除了定义额外的XML配置文件别无他选了。因此,我强烈建议您在集成测试中使用Springockito-annotations,这样你可以最大限度减少测试用例对生产代码的影响,也能消除维护额外XML配置文件的负担。
附言
为Spring工程写集成测试真是简单多了吧,文章中的代码参考自我的GitHub。 

译文链接:http://www.codeceo.com/article/spring-test-is-easy.html
英文原文:Test Me If You Can #1 (Spring Framework)
翻译作者:码农网 – Sandbox Wang

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • JAVA中Collections.sort()方法使用详解

    JAVA中Collections.sort()方法使用详解

    这篇文章主要给大家介绍了关于JAVA中Collections.sort()方法使用的相关资料,Java中Collections.sort()方法是用来对List类型进行排序的,文中通过代码将使用的方法介绍的非常详细,需要的朋友可以参考下
    2024-05-05
  • MyBatis-Plus数据库配置与数据源整合方案

    MyBatis-Plus数据库配置与数据源整合方案

    本文详细介绍了在MyBatis-Plus中进行数据库配置与数据源整合的常见方法,包括单数据源和多数据源的配置步骤,以及如何使用SpringBoot的自动配置和手动配置来管理数据源,通过合理的配置,开发者可以简化数据库操作,实现高效的数据库管理和复杂的应用架构
    2025-02-02
  • 详解Java中信号量Semaphore的使用

    详解Java中信号量Semaphore的使用

    在Java中,正确地管理并发是一件既挑战又有趣的事情,当谈到并发控制,就不得不说Java中的一个非常强大的工具,就是Semaphore,下面我们就来看看Java中信号量Semaphore的具体使用吧
    2024-01-01
  • JAVA中4种解析XML文件的方法

    JAVA中4种解析XML文件的方法

    这篇文章主要介绍了JAVA中4种解析XML文件的方法,文中示例代码非常详细,帮助大家更好的了解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • SpringBoot实现快递物流查询功能(快递鸟)

    SpringBoot实现快递物流查询功能(快递鸟)

    本文将基于springboot2.4.0实现快递物流查询,物流信息的获取通过快递鸟第三方实现,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-10-10
  • JAVA自定义注解详情

    JAVA自定义注解详情

    这篇文章主要介绍了Java自定义注解,结合实例形式总结分析了java常见的自定义注解类型、功能、用法及操作注意事项,需要的朋友可以参考下
    2021-10-10
  • 详解SpringBoot中@PostMapping注解的用法

    详解SpringBoot中@PostMapping注解的用法

    在SpringBoot中,我们经常需要编写RESTful Web服务,以便于客户端与服务器之间的通信,@PostMapping注解可以让我们更方便地编写POST请求处理方法,在本文中,我们将介绍@PostMapping注解的作用、原理,以及如何在SpringBoot应用程序中使用它
    2023-06-06
  • Java中Object类常用的12个方法(小结)

    Java中Object类常用的12个方法(小结)

    Java 中的 Object 方法在面试中是一个非常高频的点,本文主要介绍了Java中Object类常用的12个方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • IntelliJ IDEA 2022.1.1创建java项目的详细方法步骤

    IntelliJ IDEA 2022.1.1创建java项目的详细方法步骤

    最近安装了IntelliJ IDEA 2022.1.1,发现新版本的窗口还有些变化的,所以下面这篇文章主要给大家介绍了关于IntelliJ IDEA 2022.1.1创建java项目的详细方法步骤,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • java 算法之归并排序详解及实现代码

    java 算法之归并排序详解及实现代码

    这篇文章主要介绍了java 算法之归并排序详解及实现代码的相关资料,需要的朋友可以参考下
    2017-03-03

最新评论