Mock浅析和简单实践

1 篇文章 0 订阅
1 篇文章 0 订阅

一、背景

某个服务或前端依赖一个服务接口,该接口可能依赖多个低层服务或模块,或第三方接口,这种情况下需要搭建多个底层模块多套测试环境,比较痛苦,如果mock掉第一级的服务接口,可以节约不少人力,同时规避了可能由第三方服务导致的问题。

目前常见服务或接口协议主要两种,一种是RPC,另一种是HTTP/HTTPS,mock原理都类似,要么是修改原服务地址为Mock服务地址,要么是拦截原服务的请求Mock返回值,总之就是构造一个假的服务,替代原有服务。

二、Mock实现方式

只介绍下HTTP/HTTPS协议的mock实现,RPC不做深究,原理都类似。Mock实现常见两种方式,一种是通过代理抓取待测服务请求并控制返回值;另一种是直接将待测服务指向Mock服务地址,替代下游原始真实服务。

第一种通过替换待测服务为Mock Gateway地址抓取请求并控制返回值来实现,(或者简单点,直接用Mock Service地址来替换待测服务地址也可以,更简单)通过Gateway网关转发请求,下游再设具体mock服务,可以是一个mock服务直接返回预期的mock值,也可以是proxy服务继续代理请求到其他地址,或redirect服务转发到某个特殊的地址等等方式。

如果自己搭建,建议使用java技术栈,Mock Gateway和Mock Service可以使用springcloud或springboot来实现,比较简单,mock策略和数据可以使用mysql或redis或es来存储,或者放到内存中也是可以的。

第二种直接使用正向代理代理请求,拦截请求,常用工具有charles、fiddler,mitmproxy等,工具比较成熟,直接使用即可,不做深入讨论,如果持久化Mock数据,建议使用mitmproxy,技术栈是python,可自行研究

三、Mock实践实例

介绍下第一种mock方案实践方案,技术栈选用springcloud+zuul+mybatis+mysql+keytools

  • 工程1:mock-gateway用于实现拦截请求,接受到请求后,充当路由功能
  • 工程2:mock-service用于接受mock-gateway转发来的请求,指定mock规则和mock数据返回
  • 工程3:proxy-service用于接受mock-gateway转发来的请求,继续透传请求或其他mock规则改变请求

 

  • 创建数据库和表
    • #存储mock规则,包含所有mock、proxy、redirect等规则数据
      -- auto-generated definition
      create table mock_app
      (
        id               int auto_increment,
        name             varchar(200) null,
        description      varchar(200) null,
        request_type     varchar(20)  null,
        request_uri      varchar(200) null,
        request_query    longtext     null,
        request_body     longtext     null,
        request_header   longtext     null,
        mock_data        longtext     null,
        redirect_type    varchar(200) null,
        redirect_url     varchar(200) null,
        redirect_query   longtext     null,
        redirect_body    longtext     null,
        redirect_header  longtext     null,
        proxy_url_perfix varchar(200) null,
        is_active        tinyint(1)   null,
        create_time      timestamp    not null,
        update_time      timestamp    not null,
        approver         varchar(200) not null,
        constraint mock_app_id_uindex
        unique (id)
      );
      
      alter table mock_app
        add primary key (id);
      
      
      #存储mock策略,mock_app表中同一条mock数据根据mock策略返回不同的值
      -- auto-generated definition
      create table mock_strategy
      (
        id                     int auto_increment,
        name                   varchar(200) not null,
        description            varchar(200) not null,
        mock_response_strategy int          not null,
        create_time            timestamp    not null,
        update_time            timestamp    not null,
        approver               varchar(200) not null,
        constraint mock_strategy_id_uindex
        unique (id)
      );
      
      alter table mock_strategy
        add primary key (id);
      
      

       

  • mock-gateway
    • 直接创建一个springcloud工程,不会的自己搜索下,很简单,官网直接可以生产工程目录
    • 添加springcloud主类
      package com.personal.mock;
      
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
      
      
      @SpringBootApplication
      @MapperScan(basePackages = "com.personal.mock.dao")
      @EnableZuulProxy
      public class MockApplication {
      
      	public static void main(String[] args) {
      		SpringApplication.run(MockApplication.class, args);
      	}
      }
      #properties配置文件
      
      #gateway服务端口
      server.port=10086
      #添加zuul拦截条件
      zuul.routes.root.path=/*
      zuul.routes.root.url=http://127.0.0.1:10086/
      
      #数据库相关配置
      spring.datasource.url = jdbc:mysql://localhost:3306/mock?characterSet=utf8mb4&useSSL=false
      spring.datasource.username = root
      spring.datasource.password = QWER1234qwer
      
      spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      spring.datasource.filters=stat
      spring.datasource.maxActive=20
      spring.datasource.initialSize=1
      spring.datasource.maxWait=60000
      spring.datasource.minIdle=1
      spring.datasource.timeBetweenEvictionRunsMillis=60000
      spring.datasource.minEvictableIdleTimeMillis=300000
      spring.datasource.validationQuery=select 'x'
      spring.datasource.testWhileIdle=true
      spring.datasource.testOnBorrow=true
      spring.datasource.testOnReturn=true
      spring.datasource.poolPreparedStatements=true
      spring.datasource.maxOpenPreparedStatements=20
      
      #下游服务配置
      mock.request.address=http://127.0.0.1:10010/mock/request
      mock.response.address=http://127.0.0.1:10010/mock/response
      proxy.address=http://127.0.0.1:10011/proxy
      redirect.address=http://127.0.0.1:10012/redirect

       

    • 添加过滤器,过滤请求,过滤规则也在这里实现即可
      package com.personal.mock.filter;
      
      import com.alibaba.fastjson.JSONObject;
      import com.netflix.zuul.ZuulFilter;
      import com.netflix.zuul.context.RequestContext;
      import com.personal.mock.common.MockStrategyEnum;
      import com.personal.mock.po.MockApp;
      import com.personal.mock.route.MockZuulRoute;
      import com.personal.mock.route.MockZuulRouteLocator;
      import com.personal.mock.service.FilterService;
      import com.personal.mock.service.RequestService;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.stereotype.Component;
      
      import javax.servlet.http.HttpServletRequest;
      import java.util.Iterator;
      import java.util.Map;
      
      import static com.personal.mock.common.Constant.*;
      import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
      
      /**
       * author: zhaoxu
       * date: 2019/4/30 上午10:07
       */
      @Component
      public class MockPreFilter extends ZuulFilter {
      
          private static Logger logger = LoggerFactory.getLogger(MockPreFilter.class);
      
          @Value(value = "${mock.request.address}")
          private String mockRequestAddress;
          @Value(value = "${mock.response.address}")
          private String mockResponseAddress;
          @Value(value = "${proxy.address}")
          private String proxyAddress;
          @Value(value = "${redirect.address}")
          private String redirectAddress;
      
      
          @Autowired
          FilterService filterService;
      
          @Autowired
          RequestService requestService;
      
          @Autowired
          MockZuulRouteLocator mockZuulRouteLocator;
      
          @Override
          public String filterType() {
              return PRE_TYPE;
          }
      
          @Override
          public int filterOrder() {
              return 4;
          }
      
          @Override
          public boolean shouldFilter() {
              return true;
          }
      
          @Override
          public Object run() {
              RequestContext ctx = RequestContext.getCurrentContext();
              HttpServletRequest httpServletRequest = ctx.getRequest();
      
              logger.info(String.valueOf(requestService.getRequestHeader()));
              logger.info(requestService.getMethod());
              logger.info(requestService.getQueryString());
              logger.info(requestService.getRequestURI());
              logger.info(String.valueOf(requestService.getRequestBody()));
      
              logger.info("request uri: "+httpServletRequest.getRequestURI());
              Map<MockApp, Integer> map = filterService.getFilterInfo(httpServletRequest);
              if (null != map){
                  Iterator <MockApp>iterator = map.keySet().iterator();
                  MockApp mockApp = iterator.next();
                  Integer mockStrategyId = map.get(mockApp);
                  String path = mockApp.getRequestUri();
                  Integer mockAppId = mockApp.getId();
                  String url = null;
                  try{
                      MockStrategyEnum mockStrategyEnum = MockStrategyEnum.getMockStrategyByStrategyId(mockStrategyId);
                      switch (mockStrategyEnum) {
                          case MOCK_RESPONSE_DIRECT:
                              url = mockResponseAddress;
                              logger.info("【正常,gateway转发给下级服务处理】" +url);
                              break;
                          case MOCK_REQUEST_RETURN:
                              url = mockRequestAddress;
                              logger.info("【正常,gateway转发给下级服务处理】" +url);
                              break;
                          case REQUEST_REDIRECT:
                              url = redirectAddress;
                              logger.info("【正常,gateway转发给下级服务处理】" +url);
                              break;
                          case PROXY:
                              url = proxyAddress;
                              logger.info("【正常,gateway转发给下级服务处理】" +url);
                              break;
                      }
                  } catch(NullPointerException e){
                      logger.error("【异常,没有匹配到策略,详情如下:】");
                      logger.error("【mock_app.id=%d 对应的mock_strategy.mock_response_strategy配置异常,请检查】");
                  }
                  MockZuulRoute mockZuulRoute = new MockZuulRoute(mockAppId.toString(),path,url);
                  mockZuulRouteLocator.updateRoutes(mockZuulRoute);
                  ctx.addZuulRequestHeader(MOCK_STRATEGY_ID,mockStrategyId.toString());
                  ctx.addZuulRequestHeader(MOCK_APP_ID,mockAppId.toString());
                  ctx.addZuulRequestHeader(REQUEST_URI,httpServletRequest.getRequestURI());
              } else {
                  logger.error("【异常,没有匹配到mock数据,请检查】");
                  ctx.setSendZuulResponse(false);
                  ctx.setResponseStatusCode(401);
                  JSONObject jsonObject = new JSONObject();
                  jsonObject.put("errorMessage","【呃呃呃, 你的请求没有匹配到数据库的mock数据,请检查】");
                  ctx.setResponseBody(jsonObject.toJSONString());
                  ctx.getResponse().setContentType("application/json;charset=UTF-8");
              }
              return null;
          }
      }
      

       

    • mock-service、mock-proxy服务与mock-gateway代码结构类似,去掉请求过滤器即可

po层和dao层的代码就不粘贴了,类似与mybatis,后续我会把代码放到github上

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值