Spring Boot、Dubbo项目Mock测试踩坑与总结

发表于:2019-1-04 21:15

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:周立    来源:itmuch

#
测试
分享:
  本文是对Spring Boot、Dubbo项目进行Mock测试的总结与踩坑实录。
  搜索了一圈,居然没发现类似的文章,莫非用Dubbo的朋友们都不Mock测试,或者有其他的办法测试吗?
  简单总结了一下,希望对大家能有一定参考意义。
  背景
  手上有个整合了Dubbo的Spring Boot应用,在应用中需要消费其他服务的API。由于我依赖的服务并不由我所在的项目组维护(对方可能接口中途会发生变化,甚至,有时候可能并未启动)。
  集成测试成本略高,故而想办法Mock测试。以RemoteApi为例,这是一个远程的API。我这一侧(消费者)的代码如下:
   @Service
  public class MyApi {
  @Reference
  private RemoteApi remoteApi;
  public String hold() {
  return remoteApi.hold();
  }
  }
   由代码可知,MyApi调用了一个远程的API RemoteApi。
  下面我们来mock测试。
  整合powermock
  经过调研,笔者选择powermock作为项目的mock工具。
  加依赖:
   <dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>1.6.6</version>
  </dependency>
  <dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito</artifactId>
  <version>1.6.6</version>
  <scope>test</scope>
  </dependency>
  <dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4-rule</artifactId>
  <version>1.6.6</version>
  <scope>test</scope>
  </dependency>
  <dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-classloading-xstream</artifactId>
  <version>1.6.6</version>
  <scope>test</scope>
  </dependency>
  测试启动类:
   @SpringBootApplication
  public class ConsumerTest {
  public static void main(String[] args) {
  SpringApplication.run(ConsumerTest.class, args);
  }
  @Bean
  public RemoteApi RemoteApi() {
  RemoteApi remoteApi = PowerMockito.mock(RemoteApi.class);
  PowerMockito.when(remoteApi.hold())
  .thenAnswer(t -> "我是Mock的API。");
  return remoteApi;
  }
  }
   由代码可知,我在这里mock了一个RemoteApi,当调用Mock的RemoteApi.hold()方法时,返回
  
 我是Mock的API。
  测试类:
   @RunWith(SpringJUnit4ClassRunner.class)
  @SpringBootTest(classes = ConsumerTest.class)
  public class MyApiTest {
  @Autowired
  public MyApi myApi;
  @Test
  public void hold() {
  Assert.assertEquals("我是Mock的API。", this.myApi.hold());
  }
  }
   执行单元测试,发现Mock并没有成功,Dubbo依然会尝试调用远程API,而并非笔者Mock的RemoteApi。
  分析
  Mock没有成功,为什么呢?我们不妨将测试类代码修改成如下:
   @RunWith(SpringJUnit4ClassRunner.class)
  @SpringBootTest(classes = ConsumerTest.class)
  public class MyApiTest {
  @Autowired
  private ApplicationContext applicationContext;
  @Before
  public void before() {
  MyApi myApi = applicationContext.getBean(MyApi.class);
  RemoteApi fromMyApi = myApi.getRemoteApi();
  RemoteApi fromSpring = applicationContext.getBean(RemoteApi.class);
  System.out.println("MyApi中注入的RemoteApi是:" + fromMyApi);
  System.out.println("Spring容器中注入的RemoteApi是:" + fromSpring);
  }
  @Autowired
  public MyApi myApi;
  @Test
  public void hold() {
  Assert.assertEquals("我是Mock的API。", this.myApi.hold());
  }
  }
  从代码不难发现,我在执行单元测试之前,分别打印MyApi对象中注入的RemoteApi,以及Spring容器中的RemoteApi(@Bean所注解的方法,new出来的对象,必然在Spring容器中)。
  执行后,打印结果如下:
   MyApi中注入的RemoteApi是:com.alibaba.dubbo.common.bytecode.proxy0@541afb85
  Spring容器中注入的RemoteApi是:remoteApi
   由打印结果可知,MyApi中注入的RemoteApi和容器中的RemoteApi,压根不是一个实例。
  由该结果,可以知道2点:
  Dubbo的@Reference注解拿到的一个代理;
  @Reference生成的代理并不在Spring容器中(如果Dubbo的Reference的代理也是容器中,那么容器中应该有2个RemoteApi实例,那么调用getBean()应当报错);
  解决
  原因我们知道了,要如何解决呢?答案很简单——如果我们在执行单元测试之前,将StoreApi中注入的RemoteApi换成Spring容器中的实例(即我们Mock的那个对象),那么问题就可以得到就解决。
   @RunWith(SpringJUnit4ClassRunner.class)
  @SpringBootTest(classes = ConsumerTest.class)
  public class MyApiTest {
  @Autowired
  private ApplicationContext applicationContext;
  @Before
  public void before() {
  MyApi myApi = applicationContext.getBean(MyApi.class);
  RemoteApi fromSpring = applicationContext.getBean(RemoteApi.class);
  myApi.setRemoteApi(fromSpring);
  }
  @Autowired
  public MyApi myApi;
  @Test
  public void hold() {
  Assert.assertEquals("我是Mock的API。", this.myApi.hold());
  }
  }
    再次执行,就会发现,现在已经可以正常Mock了。
  源码分析
  以上已经提供了解决方案。那么,@Reference注解究竟干了哪些事情呢?我们不妨分析一下。搜索@Reference注解被哪些地方使用,可找到以下代码:
 com.alibaba.dubbo.config.spring.AnnotationBean#postProcessBeforeInitialization
   以该代码是我们定位问题的入口,由此,我们可以定位到以下两个方法:
   com.alibaba.dubbo.config.ReferenceConfig#init
  com.alibaba.dubbo.config.ReferenceConfig#createProxy
  其中,
  createProxy方法用于创建代理对象;
  init方法用来判断是否已经初始化,如果没有初始化,就会调用createProxy创建代理对象。过程比较简单,不贴了。
  了解Dubbo如何创建对象后,我们来看看Dubbo是如何将代理对象设置到MyApi的,如下图。
  分析至此,大家应该能够了解原因了——
  @Reference创建了一个代理;
  Dubbo自身做了一些判断,如果发现没有初始化,就会创建一个代理;
  在postProcessBeforeInitialization 方法中,从Spring容器中拿到MyApi对象,并将这个代理对象设到MyApi实例中。
  Going Far
  我们已经知道,是@Reference注解搞的鬼,除了以上解决方案,还可以弄一个类,转一下。
  即:原调用链:
  MyApi —> RemoteApi
  改为:
  MyApi —> 一个转换的类,啥都不干,用了@Service注解,在里面调用RemoteApi的方法 —> RemoteApi
  WHATS MORE OVER
  如果使用xml配置,不存在该问题,可以很简单地Mock。
  配套代码
  https://github.com/itmuch/spring-boot-dubbo-mock-sample

     上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号