1. 程式人生 > >自動化測試如何訪問不同的環境對應服務例項

自動化測試如何訪問不同的環境對應服務例項

首先介紹做一下場景介紹:

  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實現的話,啟動的時候就已經載入完成了,無法實現動態載入

 

宣告:該文章參考公司同事(章帥)的文章