一直以來,做爲互聯網軟件工程師接觸最多的事務之一即是持續集成(Continuous integration,簡稱 CI)。持續集成儼然已成爲主流互聯網軟件開發流程中一個重要的環節。現今有贊內部在實踐持續交付(Continuous delivery,簡稱 CD),它能夠被當作是後持續集成時代的產物。須要強調的是,不論是 CI 仍是 CD,更多的是強調做爲軟件開發交付過程當中的實踐,而一旦交付到生產環境CI和CD就無能爲力了。有贊線上撥測系統正是爲了彌補這一不足。現有的線上保障手段可分爲運維層面、產品層面、安全層面、服務層面和測試層面等維度。本文重點介紹咱們在測試層面的實踐。前端
咱們作測試線上撥測系統的初衷有如下幾點:shell
在此以前這些場景都須要測試人員手工介入,靈活度敏捷度都很是差。有了這套系統後,測試人員能夠增長本身關注的場景,場景能夠經過主動觸發和定時觸發來執行,經過告警系統通知到有關人員,作到第一時間排查問題,減小故障影響,下降故障時長。json
1.0 版本咱們使用通用的 SpringWeb 搭建,有贊內部稱爲線上機器人檢查。系統結構以下c#
1.0 版系統架構圖安全
系統主要爲三個模塊:網絡
1.0版流程圖架構
系統將用例分爲基礎用例和場景用例,支持場景併發或者順序同步執行。具體執行策略由用例設計者結合具體狀況在用例開發過程當中設定。併發
基礎版知足了最小可用,這種方式優勢在於前期可以快速投入使用,且對於常常寫集成用例的人來講成本不高,但對其餘人(測試新人、開發、運維等)則否則。歸納而言,其缺點主要集中在如下幾點:框架
因爲這些不可規避的問題,咱們從新設計併發布了 2.0 版本。對應解決以上問題:運維
新版系統架構圖以下
2.0版系統架構圖
用例模型以下:
字段 | 是否必填 | 說明 |
---|---|---|
用例名稱 | 是 | 建議命名格式:「用例類型:服務:方法」 |
用例類型 | 是 | 兩種類型可選http或dubbo |
用例描述 | 是 | 場景描述 |
所屬業務 | 是 | 用例所屬業務閾 |
請求url | 否 | http協議調用的url |
請求頭 | 否 | http header |
請求參數 | 否 | http或dubbo的請求入參。支持動態參數注入實現用例間依賴 |
服務名稱 | 否 | 對應請求dubbo協議的接口名(包名+類名) |
請求方法 | 是 | http協議:GET、POST、PUT等;dubbo協議:方法名 |
斷言 | 是 | 支持多個 |
是否開啓 | 否 | 控制開關,關閉後再也不運行。默認開啓 |
是否登陸 | 否 | 開啓後,使用默認帳號進行登陸操做。默認不開啓 |
是否重試 | 否 | 開啓後,⽤例失敗重試1次。默認否 |
前/後置檢查 | 否 | 執行⽤例前/後,先執行前/後置檢查,失敗則中斷 |
*此處略去了部分有贊內部使用的字段
爲了更直觀展現線上業務的健康情況咱們增長了豐富前端報表
數據展現
新版本與老版本的主要區別在於:
任務執行流程圖以下:
2.0版流程圖
任務執行引擎經過不一樣的工做線程實現。不一樣業務用例併發執行,業務內部用例串行執行。系統根據不一樣的用例的類型(http/dubbo)分發到具體任務流中。
核心類設計
從用例的複雜度上講,咱們的用例主要分爲兩大類:單一場景的基礎用例和複雜場景的組合用例。組合用例是在基礎用例的基礎上進行必定的集成,用例的輸入輸出存在必定的依賴。咱們實現用例依賴的方式有兩種:
第一種方式,在配置用例的時候,給它一個前置用例,固然前置用例也是在平臺中管理的。這樣當執行到該用例的時候,執行引擎會先去執行前置用例。
第二種方式,針對 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 版系統架構圖
插件化的設計思想以下:
用例標準經過接口的形式對外提供,封裝成jar包暴露出來。用例設計者直接依賴該jar包並實現指定接口便可。用例接口定義以下:
public interface AbstractTestCase { CaseResult before(); CaseResult run(); void after(); }
用例開發完成後打包成 jar 包上傳到平臺,一個 jar 包中可包含一個用例也能夠包含多個用例。
jar 包上傳後平臺要作的事情以下:
獲取 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; }
將來有贊線上撥測系統會提供更豐富的功能,例如更靈活的用例執行策略,核心用例執行頻率更高,邊緣業務執行頻率下降;更全面的報警策略,各個業務方能夠自由定製關心的用例,線上問題第一時間觸達;支持多機房,目前該系統只在單機房進行部署,有贊核心業務已完成多機房部署,撥測系統也會隨之調整;系統支持分佈式,爲了防範系統單點故障,將來還會考慮進行分佈式部署。
目前這套系統能夠保障測試同窗第一時間知曉有贊線上核心業務異常,未來保障的業務廣度和深度會進一步提升,成爲有贊線上質量保障相當重要的一環。