zoukankan      html  css  js  c++  java
  • 自动化测试如何访问不同的环境对应服务实例

    首先介绍做一下场景介绍:

      1、我们公司的测试环境比较复杂,预发环境(UAT)一套,SIT环境4套,DEV环境7套。我是负责中台模块的测试,功能类似一个订单中心,但是功能相对比较复杂。网关进来的95%以上的请求都要我负责的模块来处理(不论线上业务还是线下业务,因此所有的环境都要经过我负责模块。

         2、我们公司使用的grpc微服务框架,而我负责的中台模块,都是通过grpc的微服务接口(不提供http接口),对于测试来讲,这是个不幸的消息。

    那么我们中台的接口自动化测试是如何来实现的呢?

    这个是完整 RPC 架构图

    一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等

    • 客户端(Client):服务调用方。
    • 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
    • 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
    • 服务端(Server):服务的真正提供者。
    • Network Service:底层传输,可以是 TCP 或 HTTP。

    了解上面的基本知识。现在来介绍我是如何实现的。

    1、创建连接到远程服务器的 channel
    2、构建使用该channel的客户端stub
    3、调用服务方法,执行RPC调用
    4、封装成Controller

     构建客户端stub

    public class Client {
    
    
        //样例 stub
        private DemoServiceGrpc.DemoServiceBlockingStub demoServiceBlockingStub;
        //原生的stub 点对点测试
        public Client() {
            ManagedChannel channel = null;
            try {
                String ip =PropertiesUtils.getValue("****.grpc.ip");
                String port = PropertiesUtils.getValue("****.grpc.port");
                channel = ManagedChannelBuilder.forTarget("static://" + ip + ":" + port).usePlaintext().build();
            } catch (Exception e) {
                e.printStackTrace();
            }
            demoServiceBlockingStub = DemoServiceGrpc.newBlockingStub(channel);
            
        }
        public DemoServiceGrpc.DemoServiceBlockingStub getdemoServiceBlockingStub() {
            return demoServiceBlockingStub;
        }
    }

     封装Controller:

     

    那么简单的http 接口服务就实现了。接下来重点来了,如何实现部署一台服务访问不同环境呢????

    具体实现:

    基于spring提供原生的 AbstractRoutingDataSource ,参考一些文档自己实现切换

    1、 为了区分不同环境的配置,采用了application-{}.yaml文件来隔离, 然后通过application.yaml文件来控制加载所有的配置文件

    2、application.yaml配置

    3、在Springboot的启动类上,排除掉datasource自动配置

    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class GrpcApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(GrpcApplication.class, args);
        }
    
    }

    4、新建一个EnvContext类,采用ThreadLocal的方式,对每个请求线程的环境变量进行隔离,这里容易遇到坑,springboot都是内嵌的tomcat启动模式,如果tomcat设置了链接的重用规则,那么如果env的信息没有被清除,可能会导致错误加载配置

    /**
     * 用来存放环境的变量,用于动态的去切换
     */
    public class EnvContext {
     
        public static ThreadLocal<String> envThreadLocal = new InheritableThreadLocal<>();
     
        public static String getEnv(){
            return envThreadLocal.get();
        }
     
        public static void setEnv(String env){
            envThreadLocal.set(env);
        }
     
        public static void clear(){
            envThreadLocal.remove();
        }
    }

    5、创建一个DynamicDataSource, 这里继承了AbstractRoutingDataSource,动态数据源类集成了Spring提供的AbstractRoutingDataSource类,AbstractRoutingDataSource 中获取数据源的方法就是 determineTargetDataSource,而此方法又通过 determineCurrentLookupKey 方法获取查询数据源的key。

    public class DynamicDataSource extends AbstractRoutingDataSource {
     
     
        @Override
        protected Object determineCurrentLookupKey() {
            return EnvContext.getEnv();
        }
    }

    6、定义一个枚举类,放入所有的环境信息

    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public enum EnvEnum {
    
        DEV1("dev1","开发环境dev1"),
        DEV2("dev2","开发环境dev2"),
        DEV3("dev3","开发环境dev3"),
        DEV4("dev4","开发环境dev4"),
        DEV5("dev5","开发环境dev5"),
        DEV6("dev6","开发环境dev6"),
        DEV7("dev7","开发环境dev7"),
        SIT1("sit1","集成环境SIT1"),
        SIT2("sit2","集成环境SIT2"),
        SIT3("sit3","集成环境SIT3"),
        SIT4("sit4","集成环境SIT4"),
        UAT("uat","集成环境UAT");
        public String env;
        public String desc;
    }

    7、重点来了,我们通过AOP, 去拿到每次http的请求头中的header信息,来动态的切换EnvContext中的env配置

    @Aspect
    @Component
    @Slf4j
    public class EnvAop {
    
        public ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping ) && @annotation(io.swagger.annotations.ApiOperation))")
        public void ex(){}
    
    
        @Around("ex()")
        public Object envAop(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object result;
            try {
                //获取每个请求的header,拿到环境变量的参数,存入ThreadLocal中,供每个线程使用
                RequestAttributes ra = RequestContextHolder.getRequestAttributes();
                ServletRequestAttributes sra = (ServletRequestAttributes) ra;
                HttpServletRequest request = sra.getRequest();
                // 获取请求头
                Enumeration<String> enumeration = request.getHeaderNames();
                //
                String env = request.getHeader("env");
                if(StringUtils.isEmpty(env)){
                    log.info("~~~~ 拦截到http请求,环境变量信息为空,设置为默认dev1", env);
                    env = EnvEnum.DEV1.env;
                }
                log.info("~~~~ 拦截到http请求,环境变量信息为{}", env);
                EnvConfig.envThreadLocal.set(env);
                result = proceedingJoinPoint.proceed();
    
            } finally {
                //请求结束后,将环境变量的信息从ThreadLocal中移除
                EnvConfig.clear();
                log.info("~~~~ http请求结束,重置env的信息为{}" , EnvConfig.getEnv());
            }
            return result;
        }
    }

    8、编写一个工具类,动态获取Spring的容器ApplicationContext

    @Component
    public class SpringContextUtil {
    
        @Resource
        private ApplicationContext applicationContext;
    
        private static ConfigurableApplicationContext context;
        private static BeanFactory factoryBean;
    
        @PostConstruct
        public void init() {
            context = (ConfigurableApplicationContext) applicationContext;
            factoryBean = context.getBeanFactory();
        }
    
        public static BeanFactory getFactoryBean() {
            return factoryBean;
        }
    
        public static ConfigurableApplicationContext getApplicationContext() {
            return context;
        }
    }

    9、然后编写一个配置信息动态读取工具类,每次请求进来,env会动态切换,然后工具类会自动拼装env信息去读取

      

    public class PropertiesUtils {
        public static String getValue(String key) throws Exception {
            Environment environment = (Environment) SpringContextUtil.getApplicationContext().getBean("environment");
            String value = environment.getProperty(EnvConfig.getEnv() + "." + key);
            if(StringUtils.isEmpty(value)){
                throw new Exception("配置信息获取失败,请检查application-"+ EnvConfig.getEnv()+".yaml文件!, key = " + key + " , env = " + EnvConfig.getEnv());
            }
            return value;
        }
    }

    到此  实现通过http请求 中header中配置env参数来实现  动态切换服务器(以此类推可以修改同过parame或者url中的参数来实现动态切换服务器)

     重点注意:

       实现client 的连接的方法不能通过Springboot  的@service @Autowired来实现  不然无法实现动态切换服务器  也就是Controller里面每次使用client的时候  都要new

            因为通过Bean实现的话,启动的时候就已经加载完成了,无法实现动态加载

     

    声明:该文章参考公司同事(章帅)的文章

     

      

  • 相关阅读:
    apache启用gzip压缩方法--转载自http://www.cnblogs.com/linzhenjie/archive/2013/03/05/2943635.html
    yii 主从数据库分离-转载http://www.yiichina.com/doc/guide/2.0/db-dao
    服装尺寸
    php 同步因子的并发处理
    NFC会员管理-转载自http://technews.cn/2014/09/13/nfc-sticker/
    Redis 利用锁机制来防止缓存过期产生的惊群现象-转载自 http://my.oschina.net/u/1156660/blog/360552
    移动端H5页面的设计稿尺寸大小规范-转载自http://www.chinaz.com/design/2015/1103/465670.shtml
    服饰行业淘宝商城店铺首页设计报告-转载自http://bbs.paidai.com/topic/88363
    网页设计的标准尺寸
    hdu2099
  • 原文地址:https://www.cnblogs.com/emars/p/12750492.html
Copyright © 2011-2022 走看看