跟着小程学微服务-Mock自动化系统的原理及实现
一、前言
在之前的文章 http://blog.csdn.net/u013970991/article/details/54862772 中已经介绍了“自动化Mock系统0.9版本”,今天我将和大家一起探讨我们的“自动化Mock系统1.0版本”。
二、测试人员面临的测试问题
我公司目前用的是基于Dubbo的微服务改造,服务之间的调用链路冗长,每个服务又是单独的团队在维护,每个团队又在不断的演进和维护各个服务,那么对测试人员将是非常大的挑战。
测试人员每次进行功能测试的时候,测试用例每次都需要重新写一遍,无法将测试用例的数据沉淀,尤其是做自动化测试的时候,测试人员准备测试数据就需要很长时间,效率非常低。
目前接口自动化测试框架也多种多样,testng,junit,Fitnesse等,但都需要测试人员具备测试代码编写能力,如果要做好和手工接口测试一样效果的自动化测试更是需要大量的代码堆积,后期维护代码成本非常大。因此做成简单配置用例流,无需编写测试代码的系统是更贴合实际工作要求。
举个例子:拿互联网支付系统来说,某个团队新增了支付交易的需求,这时候要进行测试,测试人员除了要测试支付交易需求本身是否正确,同时也要结合上下游的服务整体进行回归测试,这时候开发人员往往在支付交易系统中采用“硬编码”的方式对上下游的系统进行“挡板”,如果测试人员对测试数据有所调整那么“挡板”也要跟着调整,同时在项目正式上线的时候,如果开发人员没有将“挡板”程序去除干净,将面临严重的线上问题。
三、Dubbo的Mock功能
1、Dubbo的Mock使用
Dubbo自带的Mock功能首先是为了做服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。
我们从官网上举一个例子来说明:
<dubbo:reference interface="com.foo.BarService" mock="force" />
我们可以在期望的reference标签上加一个mock=”force”,就可以将当前服务设置为mock。但是设置完mock属性后还没有结束,需要有一个Mock类对应我们的服务接口类。
规则如下: 
接口名 + Mock后缀,服务接口调用失败Mock实现类,该Mock类必须有一个无参构造函数。
对应到com.foo.BarService的话,则创建BarServiceMock类。
public class BarServiceMock implements BarService {
    public String sayHello(String name) {
        // 你可以伪造容错数据,此方法只在出现RpcException时被执行
        return "容错数据";
    }
}
经过以上设置后,当调用BarService进行远程调用的话,直接请求到BarServiceMock类上面进行模拟测试。
2、Dubbo Mock的原理解析
在dubbo的配置文件中 
classpath:/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.Cluster 
可以看到如下配置列表:
mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
switch=com.alibaba.dubbo.rpc.cluster.support.SwitchCluster
mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
我们可以看到配置文件中实际上有五大路由策略:
AvailableCluster: 获取可用的调用。遍历所有Invokers判断Invoker.isAvalible,只要一个有为true直接调用返回,不管成不成功。
BroadcastCluster: 广播调用。遍历所有Invokers, 逐个调用每个调用catch住异常不影响其他invoker调用。
FailbackCluster: 失败自动恢复, 对于invoker调用失败, 后台记录失败请求,任务定时重发, 通常用于通知。
FailfastCluster: 快速失败,只发起一次调用,失败立即保错,通常用于非幂等性操作。
FailoverCluster: 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟。
Dubbo中默认使用的是FailoverCluster策略,而在实际执行的过程中是FailoverCluster会被先被注入到MockClusterWrapper中,过程就是:
Cluster$Adaptive -> 定位到内部key为failover的对象 ->FailoverCluster->注入到MockClusterWrapper 
MockClusterWrapper内部会创建一个MockClusterInvoker对象。实际创建是封装了FailoverClusterInvoker的MockClusterInvoker,这样就成功地在Invoker之中植入了Mock机制。
我们来看MockClusterInvoker的内部实现: 
* 如果在没有配置之中没有设置mock,那么直接把方法调用转发给实际的Invoker(也就是FailoverClusterInvoker)。
String mockValue = directory.getUrl().getMethodParameter(
        invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
    if (mockValue.length() == 0 || mockValue.equalsIgnoreCase("false"))
    {
        //no mock
        result = this.invoker.invoke(invocation);
    }  
- 如果配置了强制执行Mock,比如发生服务降级,那么直接按照配置执行mock之后返回。
 
else if (mockValue.startsWith("force"))
{
      if (logger.isWarnEnabled())
      {
         logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url: " +  directory.getUrl());
       }
      //force:direct mock
       result = doMockInvoke(invocation, null);
}  
- 如果是其它的情况,比如只是配置的是mock=fail:return null,那么就是在正常的调用出现异常的时候按照配置执行mock。
 
 try
 {
    result = this.invoker.invoke(invocation);
 }
 catch (RpcException rpcException)  {
     if (rpcException.isBiz())  {
         throw rpcException;
     }
     else
     {
     if (logger.isWarnEnabled())  {
        logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : "
     +  directory.getUrl(), rpcException);
     }
        result = doMockInvoke(invocation, rpcException);
     }
 }  
3、Dubbo Mock的适用场景
Dubbo的Mock功能主要是为了做服务降级而使用的,服务提供方在客户端执行容错逻辑,在出现RpcException(比如网络失败,超时等)时进行容错,然后执行降级Mock逻辑。自身并不适合做Mock测试系统。
四、自动化Mock系统的实现
1、Mock系统的简单用例图
2、Mock系统的架构图
为了基于Dubbo实现Mock功能,需要对Dubbo源码进行一些必要的修改,通过上面的架构图我们可以看到,实际上我们正是利用了Dubbo的Filter chain过滤器链这一机制实现的,为了方便大家更好的理解,下面将简单介绍一下Dubbo的Filter机制。
2.1、Dubbo的Filter原理分析
Filter:是一种递归的链式调用,用来在远程调用真正执行的前后加入一些逻辑,跟aop的拦截器servlet中filter概念一样的。
Filter接口定义:
@SPI
public interface Filter {
   Result invoke(Invoker<?> invoker,Invocation invocation) throws RpcException;
}
Filter的实现类需要打上@Activate注解, @Activate的group属性是个string数组,我们可以通过这个属性来指定这个filter是在consumer, provider还是两者情况下激活,所谓激活就是能够被获取,组成filter链。
List<Filter> filters =ExtensionLoader.getExtensionLoader(Filter.class).getAct ivateExtension(invoker.getUrl(),key, group);
Key就是SERVICE_FILTER_KEY还是REFERENCE_FILTER_KEY
Group就是consumer或者provider
关于SPI的详细介绍请大家参考我之前写的另一篇文章: 
http://blog.csdn.net/u013970991/article/details/52036265
ProtocolFilterWrapper:在服务的暴露与引用的过程中根据KEY是PROVIDER还是CONSUMER来构建服务提供者与消费者的调用过滤器链,Filter最终都要被封装到Wrapper中的。
public <T> Exporter<T> export(Invoker<T>invoker)throws RpcException {
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
public <T> Invoker<T> refer(Class<T> type,URL url)throws RpcException {
     return buildInvokerChain(protocol.refer(type, url),Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
构建filter链,当我们获取激活的filter集合后就通过ProtocolFilterWrapper类中的buildInvokerChain方法来构建。
for (int i = filters.size() - 1; i >= 0; i --) {
      final Filter filter = filters.get(i);
      final Invoker<T> next = last;
      last = new Invoker<T>() {
            public Result invoke(Invocation invocation)throws RpcException {
                 return filter.invoke(next, invocation);
            }
           。。。。。。。 //其他方法
       };
 }
2.2、Mock流程介绍
 
注:我们在中新加了自定义的“env=test”这样的属性配置用来标明当前环境是测试的还是正式的,用户每次通过Dubbo请求的远程服务的时候,都会首先经过我们自定义的Filter,我们自定义的Filter会首先判断当前的环境是test还是正式,如果是test的环境则直接访问Mock配置中心获取提前配置好的Mock数据并封装成用户定义的Response对象返回。
3、Mock系统的配置中心
Mock配置中心就是用户将mock数据与应用环境建立关系的系统,整个系统就像一个工作流引擎:
环境设置->应用名称设置->挡板规则设置->Facade服务接口设置->方法规则设置
环境设置
注:如果尚未映射来源IP地址到环境,则点击环境列表导航链接,进入环境列表页面,点击添加,输入源IP及环境名,点击确定按钮,实现源IP到所设环境的映射。每个用户都可以建立属于自己的测试环境。应用名称设置
 
注:创建所使用系统的应用名称,Mock配置中心默认使用中的名称作为应用名称。
挡板规则
注:每一个挡板规则都是由一个环境名称和应用名称组成的唯一挡板,在挡板设置中选择环境名称和应用名称,并且设置挡板的有效状态。Facade规则
 
注:每一个Facade就是一个Dubbo的服务接口类,在这里将自己的Facade名称与全路径与挡板名称对应,以标识哪些Facade服务接口类是属于哪个挡板的。
- 方法规则 
注:方法规则是用来设置每个Facade中的需要mock的方法的,可以对不同的方法设置方法执行时间、方法抛出的异常等等。 
4、Mock系统的其他功能
由于不少应用项目开发完后想对其进行单独压测,而很多时候应用系统和其他业务系统形成了依赖关系,如果不布署其他应用系统则无法完成压测,为了更好的支持性能测试组进行挡板压测,Mock系统支持压测功能,而Mock系统自身也可以达到单台服务器1000TPS以上(8C8G)。
跟着小程学微服务-Mock自动化系统的原理及实现的更多相关文章
- Game On Serverless:SAE 助力广州小迈提升微服务研发效能
		
作者:洛浩 小迈于 2015 年 1 月成立,是一家致力以数字化领先为优势,实现业务高质量自增长的移动互联网科技公司.始终坚持以用户价值为中心,以数据为驱动,为用户开发丰富的工具应用.休闲游戏.益智. ...
 - 微服务架构中APIGateway原理
		
背景 我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些小系统通常以提供 Rest ...
 - 三.Go微服务--令牌桶实现原理
		
1. 前言 在上一篇文章 Go微服务: 令牌桶 当中简单的介绍了令牌桶实现的原理,然后利用 /x/time/rate 这个库 10 行代码写了一个基于 ip 的 gin 限流中间件,那这个功能是怎么实 ...
 - 微服务 Zipkin 链路追踪原理(图文详解)
		
一个看起来很简单的应用,可能需要数十或数百个服务来支撑,一个请求就要多次服务调用. 当请求变慢.或者不能使用时,我们是不知道是哪个后台服务引起的. 这时,我们使用 Zipkin 就能解决这个问题. 由 ...
 - 微服务-熔断器 Hystrix 的原理与使用
		
前言 分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应. 为了应对服务雪崩, 一种常见的做法是手动服务降级. 而Hystrix的出现,给我们提供了另一种选 ...
 - 「 从0到1学习微服务SpringCloud 」01 一起来学呀!
		
有想学微服务的小伙伴没?一起来从0开始学习微服务SpringCloud,我会把学习成果总结下来,供大家参考学习,有兴趣可以一起来学!如有错误,望指正! Spring .SpringBoot.Sprin ...
 - 基于DDD的微服务设计和开发实战
		
你是否还在为微服务应该拆多小而争论不休?到底如何才能设计出收放自如的微服务?怎样才能保证业务领域模型与代码模型的一致性?或许本文能帮你找到答案. 本文是基于 DDD 的微服务设计和开发实战篇,通过借鉴 ...
 - 驱动领域DDD的微服务设计和开发实战
		
你是否还在为微服务应该拆多小而争论不休?到底如何才能设计出收放自如的微服务?怎样才能保证业务领域模型与代码模型的一致性?或许本文能帮你找到答案. 本文是基于 DDD 的微服务设计和开发实战篇,通过借鉴 ...
 - Spring Cloud微服务学习笔记
		
Spring Cloud微服务学习笔记 SOA->Dubbo 微服务架构->Spring Cloud提供了一个一站式的微服务解决方案 第一部分 微服务架构 1 互联网应用架构发展 那些迫使 ...
 
随机推荐
- gerrit代码审核工具之“error unpack failed error Missing unknown”错误解决思路
			
使用gerrit代码审核工具时遇到error: unpack failed: error Missing unknown d6d7c89bd1d77f44c5c8e99437aaffbfc0684e7 ...
 - JS 如何获取当前上一个月、下一个月和月份所含天数
			
在数据报表查询中,经常需要设置查询的日期区间,如查询2018-02-01至2018-02-28的整月数据,这时需要提供快捷整月查询按钮: 如: 一般日期年月日之间由"-"或者&qu ...
 - TSP - 状态压缩dp
			
2017-08-11 21:10:21 艾教写的 #include<iostream> #include<cstdio> #include<cstring> #in ...
 - RNAseq 流程
			
https://github.com/twbattaglia/RNAseq-workflow
 - 在 Spark 中使用 IPython Notebook
			
本文是从 IPython Notebook 转化而来,效果没有本来那么好. 主要为体验 IPython Notebook.至于题目,改成<在 IPython Notebook 中使用 Spark ...
 - 《用 Python 学微积分》笔记 3
			
<用 Python 学微积分>原文见参考资料 1. 16.优化 用一个给定边长 4 的正方形来折一个没有盖的纸盒,设纸盒的底部边长为 l,则纸盒的高为 (4-l)/2,那么纸盒的体积为: ...
 - LeetCode第[78]题(Java):Subsets(求子集)扩展——第[90]题:Subsets 2
			
题目:矩阵置0 难度:Easy 题目内容: Given a set of distinct integers, nums, return all possible subsets (the pow ...
 - 英语每日阅读---5、VOA慢速英语(翻译+字幕+讲解):美国人口普查局表示美国人受教育程度提升
			
英语每日阅读---5.VOA慢速英语(翻译+字幕+讲解):美国人口普查局表示美国人受教育程度提升 一.总结 一句话总结: a.Thirty-four percent - college degree: ...
 - LR----实现WebService测试
			
测试WebService时,需要导入WSDL:同SoapUI WSDL:http://ws.webxml.com.cn/webservices/DomesticAirline.asmx?wsdl 场景 ...
 - Android-------ListView列表中获取EditText输入的值
			
最近项目的购物车中用列表中包含了留言功能, 需要获取EditText输入的内容,当购买多件商品时,就有点棘手了. 经过查资料解决了这个功能,并写了一个案例: 效果图: 可以在商品数据用一个字段来管理留 ...