有贊線上撥測系統實踐(一)

前言

一直以來,做爲互聯網軟件工程師接觸最多的事務之一即是持續集成(Continuous integration,簡稱 CI)。持續集成儼然已成爲主流互聯網軟件開發流程中一個重要的環節。現今有贊內部在實踐持續交付(Continuous delivery,簡稱 CD),它能夠被當作是後持續集成時代的產物。須要強調的是,不論是 CI 仍是 CD,更多的是強調做爲軟件開發交付過程當中的實踐,而一旦交付到生產環境CI和CD就無能爲力了。有贊線上撥測系統正是爲了彌補這一不足。現有的線上保障手段可分爲運維層面、產品層面、安全層面、服務層面和測試層面等維度。本文重點介紹咱們在測試層面的實踐。前端

基於測試腳本的線上監控產生

咱們作測試線上撥測系統的初衷有如下幾點:shell

  1. 主動預警線上問題。有贊有不少個業務線,各個業務線有不一樣的開發測試同窗對接,咱們很難作到每次發佈都把影響面評估得十分準確。運維層面的監控更多的是被動告警,即用戶流量觸發了線上 bug,咱們纔會收到報警,用戶體驗不夠好。咱們須要在線上 bug 預警方面變被動爲主動,週期性地知曉各個業務線的健康情況。
  2. 小流量下敏捷發現線上問題。一般咱們軟件的發佈都是在凌晨流量很是低的時候進行。發佈完成後,迴歸時間長(靠手動),測試面有限(沒法作到次次發佈全量回歸)。此時須要敏捷構造一波覆蓋面全的流量,在小流量背景下,敏捷發現線上問題。
  3. 知曉緊急狀況下業務的受影響範圍以及後續收斂狀況。例如當生產環境出現網絡異常等非軟件故障時,須要清楚業務層面的影響;當網絡恢復後,須要知道業務影響是否都已經收斂。

在此以前這些場景都須要測試人員手工介入,靈活度敏捷度都很是差。有了這套系統後,測試人員能夠增長本身關注的場景,場景能夠經過主動觸發和定時觸發來執行,經過告警系統通知到有關人員,作到第一時間排查問題,減小故障影響,下降故障時長。json

基礎版

1.0 版本咱們使用通用的 SpringWeb 搭建,有贊內部稱爲線上機器人檢查。系統結構以下c#

圖片描述

1.0 版系統架構圖安全

系統主要爲三個模塊:網絡

  1. 任務調度模塊。該模塊將用例執行封裝成系統任務,使用 Spring Quartz 來定時觸發。對外提供 API 對接有贊發佈平臺,每當系統發佈上線完成後主動觸發用例執行。
  2. 測試用例模塊。包括業務訪問,斷言和告警。測試場景須要各個業務線的測試同窗投入開發。
  3. 告警模塊。對接有贊內部告警平臺。

圖片描述

1.0版流程圖架構

系統將用例分爲基礎用例和場景用例,支持場景併發或者順序同步執行。具體執行策略由用例設計者結合具體狀況在用例開發過程當中設定。併發

存在的問題

基礎版知足了最小可用,這種方式優勢在於前期可以快速投入使用,且對於常常寫集成用例的人來講成本不高,但對其餘人(測試新人、開發、運維等)則否則。歸納而言,其缺點主要集中在如下幾點:框架

  1. 業務線一旦多起來,用例代碼開發成本提升;
  2. 隨着用例數量增長,後期用例維護成本很大;
  3. 用例上線不靈活,每次用例改動須要從新發布;
  4. 沒法直觀看到運行狀況和業務覆蓋狀況;
  5. 每次執行不區分業務,全量執行;
  6. 用例代碼存在冗餘,效率比較低。

配置化和可視化

因爲這些不可規避的問題,咱們從新設計併發布了 2.0 版本。對應解決以上問題:運維

  1. 測試用例和測試場景支持配置化,能夠從管理平臺上配置;
  2. 用例配置標準化,給定標準用例結構和斷言策略;
  3. 經過管理平臺來管理本身的用例,用例改動實時生效,無需發佈;
  4. 增長前端展現,經過圖表直觀展現運行狀況和業務覆蓋狀況,方便不一樣人羣查閱;
  5. 對接發佈平臺,按照指定的應用名來區分跑哪些用例;
  6. 設計用例執行框架,實現核心代碼複用。

新版系統架構圖以下

圖片描述

2.0版系統架構圖

用例模型以下:

字段 是否必填 說明
用例名稱 建議命名格式:「用例類型:服務:方法」
用例類型 兩種類型可選http或dubbo
用例描述 場景描述
所屬業務 用例所屬業務閾
請求url http協議調用的url
請求頭 http header
請求參數 http或dubbo的請求入參。支持動態參數注入實現用例間依賴
服務名稱 對應請求dubbo協議的接口名(包名+類名)
請求方法 http協議:GET、POST、PUT等;dubbo協議:方法名
斷言 支持多個
是否開啓 控制開關,關閉後再也不運行。默認開啓
是否登陸 開啓後,使用默認帳號進行登陸操做。默認不開啓
是否重試 開啓後,⽤例失敗重試1次。默認否
前/後置檢查 執行⽤例前/後,先執行前/後置檢查,失敗則中斷
*此處略去了部分有贊內部使用的字段

爲了更直觀展現線上業務的健康情況咱們增長了豐富前端報表

圖片描述

數據展現

新版本與老版本的主要區別在於:

  1. 將執行流和數據流進行了分離,測試用例設計無需編碼,支持配置化,用例做爲數據存放到 DB 中重複使用,用例的執行引擎管理用例的執行流。
  2. 對通用的事務進行了封裝,好比登陸、切換店鋪等操做,經過統一的線程池進行管理。
  3. 支持動態參數注入,實現了用例間的相互依賴,後面再單獨介紹這塊內容。

任務執行流程圖以下:

圖片描述

2.0版流程圖

任務執行引擎經過不一樣的工做線程實現。不一樣業務用例併發執行,業務內部用例串行執行。系統根據不一樣的用例的類型(http/dubbo)分發到具體任務流中。

圖片描述

核心類設計

用例間依賴的實現

從用例的複雜度上講,咱們的用例主要分爲兩大類:單一場景的基礎用例和複雜場景的組合用例。組合用例是在基礎用例的基礎上進行必定的集成,用例的輸入輸出存在必定的依賴。咱們實現用例依賴的方式有兩種:

  1. 經過配置用例的前置後置關係。
  2. 經過參數注入。

第一種方式,在配置用例的時候,給它一個前置用例,固然前置用例也是在平臺中管理的。這樣當執行到該用例的時候,執行引擎會先去執行前置用例。

第二種方式,針對 Json 格式的入參,咱們定義以下格式進行參數注入:

$#a,b,c#$

各個字段分別表明的含義爲:

a:被依賴用例的ID
b:被依賴用例響應的字段(key值),好比:name
c:可選字段,當被依賴值位於 array 裏面時,取其 index 下標
舉例:{"code":"$#8,data,0#$","type":"$#10,type#$"}

參數注入的流程以下:

圖片描述

參數注入流程圖

斷言模塊設計

在新版系統裏面,咱們設計了四種類型的通用斷言,幾乎能夠知足咱們本身的全部應用場景。這四種類型分別是:
1.是否包含。
響應內容包含指定內容爲 true,反之爲 false。

2.非空/null。
響應內容非空/null爲 true,爲空/null爲 false。

3.JSON 特定位置的值的「相等」判斷。
這種狀況系統首先會將響應內容轉換成 json,添加斷言時須要指定待比較對象在 json 串中的座標。若是該座標上的值與指定的值相等則爲 true,反之爲 false。
那麼如何給一個 json 串的每一個值設置一個獨一無二的座標呢?考慮到 json 存在嵌套關係且 key 可能重複,咱們經過一種複合 key 的來表示這個座標,例若有以下 json:

{

      "data": {

            "list": [

                  "1",

                  "2"

            ],

            "info": {

                  "name": "<font color=#DC143C>張三</font>",

                  "age": 18

            }

      },

      "code": 200

}

對標紅的值的斷言能夠這樣表示:{"data":{"info":{"name":"張三"}}},若是返回的位置的值爲"張三"則判斷結果爲 true,不然爲 false。

4.面向 JSON 的僞代碼表達式判斷

前面三種類型的斷言僅知足了部分場景,對於一些複雜的斷言仍然沒法知足,好比上文 json 中 list size 的斷言。爲此,咱們引入第四種斷言方式---僞代碼斷言。針對 list size 的斷言咱們能夠這樣寫:

<center>getJSONObject("data")getJSONObject("list").size() > 0</center>

代碼在處理的時候會將該表達式拼接在 json 對象後進行執行。整段代碼執行的結果爲真斷言爲 true,不然爲 false。
僞代碼的動態編譯、加載和調用,採用 GroovyShell 來實現。該部分代碼實現以下:

public Result compare(String response) {
    Result result = new Result();

    // 單例獲取GroovyShell
    GroovyShell shell = SingleGroovyUtil.getGroovyShell();

    Binding binding = null;
    JSONObject jsonObject = new JSONObject();
    JSONArray  jsonArray = new JSONArray();
    Object value = null;
    try {
        if (response.startsWith("[")){
            jsonArray = JSON.parseArray(response);
            binding = new Binding();
            binding.setVariable("data", jsonArray);
            value = InvokerHelper.createScript(shell.getClass(), binding).evaluate("data." + textStatement);
        }else {
            jsonObject = JSON.parseObject(response);
            binding = new Binding();
            binding.setVariable("data", jsonObject);
            value = InvokerHelper.createScript(shell.getClass(), binding).evaluate("data." + textStatement);
        }

        if((Boolean)value) {
            result.setSuccess(true);
        }else {
            result.setSuccess(false);
            String msg = JsonUtil.findErrMsgByJsonObject(jsonObject);
            result.setMsg(String.format("斷言失敗。斷言的內容[%s], 錯誤描述[%s]", this.textStatement, msg.length()>0?msg:response));
        }

    } catch (Exception e) {
        result.setSuccess(false);
        String msg = JsonUtil.findErrMsgByJsonObject(jsonObject);
        result.setMsg(String.format("斷言時發生異常。ErrMsg=[%s],actual=[%s]", e.getMessage(), msg.length()>0?msg:response));
    } finally { // 處理完後,主動將對象置爲null
        binding = null;
    }
    return result;
}

插件化

新版系統知足了用例的可配置化以及可視化的要求,同時也犧牲了一部分的靈活性。例如一些複雜斷言的僞代碼會很是長,且可讀性不高,一不留神就會出錯;簡單的用例依賴能夠知足,複雜的用例依賴卻很難知足。好比用例 A 在某些條件下依賴用例 B,其餘條件下依賴用例 C,這種複雜依賴關係走配置化並不合適。基於以上考慮,咱們在現有的系統的基礎上又增長了插件化的特性,來支持複雜用例的接入。

圖片描述

3.0 版系統架構圖

插件化的設計思想以下:

  1. 平臺對外提供一套用例標準,測試同窗開發符合標準的用例添加到平臺便可運行。
  2. 用例與平臺徹底解耦,用例在平臺可配置。
  3. 用例支持熱插拔,平臺無需重啓。

用例標準經過接口的形式對外提供,封裝成jar包暴露出來。用例設計者直接依賴該jar包並實現指定接口便可。用例接口定義以下:

public interface AbstractTestCase {

    CaseResult before();

    CaseResult run();

    void after();
}

用例開發完成後打包成 jar 包上傳到平臺,一個 jar 包中可包含一個用例也能夠包含多個用例。

jar 包上傳後平臺要作的事情以下:

  1. 動態把 jar load 進 JVM
  2. 解析實現了 AbstractTestCase 接口的類
  3. 按照指定策略調用類中的方法
  4. 上報並展現結果數據

獲取 jar 包中實現了 AbstractTestCase 接口的代碼以下:

/**
 * 獲取jar包中某接口的實現類
 */
public static List<Class<?>> getAllImplClassesByInterface(Class c) {
    List<Class<?>> filteredList = new ArrayList<Class<?>>();
    //判斷是不是接口
    if (c.isInterface()) {
        try {
            //獲取jar包中的全部類
            List<Class> allClass = getClassesByPackageName();
            allClass.forEach(clazz -> {
                if (c.isAssignableFrom(clazz)) {
                    if (!c.equals(clazz)) {
                        filteredList.add(clazz);
                    }
                }
            });
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return filteredList;
}

將來

將來有贊線上撥測系統會提供更豐富的功能,例如更靈活的用例執行策略,核心用例執行頻率更高,邊緣業務執行頻率下降;更全面的報警策略,各個業務方能夠自由定製關心的用例,線上問題第一時間觸達;支持多機房,目前該系統只在單機房進行部署,有贊核心業務已完成多機房部署,撥測系統也會隨之調整;系統支持分佈式,爲了防範系統單點故障,將來還會考慮進行分佈式部署。

目前這套系統能夠保障測試同窗第一時間知曉有贊線上核心業務異常,未來保障的業務廣度和深度會進一步提升,成爲有贊線上質量保障相當重要的一環。

圖片描述

相關文章
相關標籤/搜索