導讀
作為背景開發 Coder,你可能會對以下場景感到似曾相識:曆史上處理過的 BUG 反複橫跳;版本相容邏輯多,修複一個 BUG 觸發了更多 BUG;上線時系統監控毫無異常,過段時間使用者投訴某個頁面無資料;改動祖傳代碼時如履薄冰,心智負擔極重。為此本文提出一個自動化測試系統,它能夠低成本實作100%的測試用例覆寫率,極大減輕管理自動化測試用例的工作量并提高測試效率,保障背景服務平穩變更。歡迎閱讀~
目錄
1 背景
1.1 接口自動化測試介紹
1.2 現狀及痛點
1.3 為什麼要自研
1.4 目标
2 自動化測試系統實作
2.1 整體架構
2.2 統一 HTTP 和 RPC 通路形式
2.3 接口參數傳遞(參數池構造)
2.4 JSON Schema 元件
2.5 JSON Path 元件
2.6 變更系統接入與排程
3 用例自動化生成
3.1 現狀以及分析思路
3.2 整體流程
3.3 流量特征分析
3.4 用例生成
3.5 用例發現與補全
3.6 流量特征應用
4 總結
01
背景
1.1 接口自動化測試介紹
顧名思義,接口測試就是對系統或者元件之間的接口進行測試,主要校驗資料的交換、傳遞以及系統間的互相依賴關系等。根據測試金字塔的模型理論,測試分為三層,分别是單元測試(Unit Tests)、服務測試(Service Tests)、UI 測試(UI Tests),而我們的接口自動化測試就是服務測試層。
單元測試會導緻工作量大幅提升,在需求快速疊代和人力緊張的背景下,很難持續推進,本文暫不讨論。而接口自動化測試容易實作、維護成本低,且收益更高,有着更高的投入産出比。
1.2 現狀及痛點
實際上我們有一個叫 WeJestAPITest 的自動化測試平台,它是基于 Facebook 開源的 Jest 測試架構搭建的,用于校驗背景的接口傳回是否符合預期。在這個平台此前運作了數年的測試,一定程度上保障了背景服務的平穩運作。
但在長期使用中我們也發現了一些痛點:
|
我們需要的不隻是一個自動化測試系統,而是一個更好用的、管理成本更低的自動化測試系統。
1.3 為什麼要自研
提到接口自動化測試工具,開源有 JMeter、Postman 等,司内也有成熟的 WeTest、ITEST 等,這些都是開箱即用的,但經過調研和評估,我們還是決定自己造一個輪子。考慮的點如下:
|
1.4 目标
結合我們遇到的痛點以及業務需求,自研的自動化測試系統應該具備以下的能力:
|
02
自動化測試系統實作
2.1 整體架構
2.2 統一 HTTP 和 RPC 通路形式
HTTP 和 RPC 請求在形式上可以被統一起來,其描述形式如下:
HTTP通路方式:http://host:port/urlpath + reqbody
RPC通路方式:rpc://ip:port/method + reqbody
通過這種統一的描述形式,再結合我們的業務架構,就可以設計一種通用的通路方式。背景的系統架構如下圖所示:
從 proxy 層往下,所有的調用都是一個個背景服務子產品,HTTP 通路的是邏輯層,RPC 通路的是服務層。那麼隻需要配置用例的歸屬子產品,通過子產品名 + Client 配置就可以對 HTTP 和 RPC 請求進行區分以及尋址。
從變更系統的角度來看,我們的上線變更也是按子產品來的。是以把用例歸屬到一個個具體的子產品,是最符合背景同學認知的做法。
是以我們通過配置子產品名這種統一的形式,為使用者提供了統一的管理方式,隻需要指定子產品名就可以任意通路 HTTP 或者 RPC 請求,其流程如下:
在紅色虛線框的流程中,隻需要配置子產品名,就可以通過子產品名擷取到 RPC 服務的所有資訊,包括其接口定義、請求包定義、回包定義,這不是一種通用能力,需要業務基于系統架構以及線上環境去拓展,但這帶來了以下便利:
|
這裡的統一包含兩部分:第一部分是通路形式的統一(子產品),降低了配置用例的成本;第二部分是資料的統一(JSON),它統一了對回包方式的校驗,降低了校驗成本。
2.3 接口參數傳遞(參數池構造)
很多業務場景的完成都是由多個接口組成的一條鍊路實作,而且這種鍊路型的自動化測試,通常會存在參數依賴關系,一個用例的入參,可能要依賴上遊響應回包的某個字段值,是以需要提取出來并傳遞給下一個接口。如下圖:
其解決方案是,通過正則或者 JSON Extracor 等提取的結果作為變量,然後再傳遞給下遊用例使用,這也是很多測試工具使用的方式,但是維護起來不夠友善,仍有進一步優化的空間。
于是我們提出了參數池的概念,将每個用例可能用到的字段都放入一個池子裡,這個池子的元素是一個個 key-value。key 是我們要使用的變量,value 則是 key 對應的取值,值得注意的是,value 既可以是一個字面值,也可以是一個 JSONPointer 的路徑,這個路徑可以從響應回包中提取變量值。
在這種方式下,不同用例間的參數依賴不再是從上一個“傳遞”到下一個,而變成了一個随取随用的池子,是以我們把它稱為參數池。同時我們通過自定義的文法,實作了一個簡單的模闆引擎,将我們引用的變量替換為池子裡的 value 值。參數池構造以及使用圖示如下:
2.4 JSON Schema 元件
下面貼一段代碼看看現有 WeJestAPITest 架構是如何對傳回值做校驗的,并分析一下它可能存在的問題:
function bookInfoBaseCases(bookInfoObject) {
it('預期 bookInfo.bookId 非空,且為字元串,且等于12345', () => {
expect(bookInfo.bookId).not.toBeNull();
expect(typeof bookInfo.bookId).toEqual('string');
expect(bookInfo.bookId).toEqual('12345');
});
}
這種校驗方式存在以下幾個問題:
|
現有架構的不便導緻了用例管理上的種種問題,而我們根據這些不便之處去反向思考,我們到底需要什麼樣的校驗方式,這種情況下我們找到了 JSON Schema。
JSON Schema 是描述 JSON 資料格式的工具,Schema 可以了解為模式或者規則,它可以限制 JSON 資料應該符合哪些模式、有哪些字段、其值是如何表現的。JSON Schema 本身用 JSON 編寫,且需要遵循 JSON 本身的文法規範。
下面以bookInfo的校驗為例,寫一份 JSON Schema 的校驗規則:
// bookInfo資訊
{
"bookId":"123456",
"title":"書名123",
"author":"作者123",
"cover":"https://abc.com/cover/123456.jpg",
"format":"epub",
"price":100
}
// 對應的JsonSchema校驗規則
{
"type": "object",
"required": ["bookId", "title", "author", "cover", "format", "price"],
"properties": {
"bookId": {
"type": "string",
"const": "123456"
},
"title": {
"type": "string",
"minLength": 1
},
"author": {
"type": "string",
"minLength": 1
},
"cover": {
"type": "string",
"format": "uri"
},
"format": {
"type": "string",
"enum": ["epub", "txt", "pdf", "mobi"]
},
"price": {
"type": "number",
"exclusiveMinimum": 0
}
}
}
通過對比,JSON Schema 的優點非常顯而易見:
|
2.5 JSON Path 元件
有了 JSON Schema 之後,我們校驗方式看似已經非常完美了。它既可以低成本的覆寫全量字段校驗,還可以很友善的進行字段類型、數值的校驗。
但實際使用中我們發現有些測試場景是 JSON Schema 覆寫不到的,比如:一條使用者評論有 createtime 和 updatetime 兩個字段,需要校驗 updatetime >= createtime。這是 JSON Schema 的短闆,它可以限制 JSON 的字段,但是它沒辦法對兩個字段進行對比;同時 JSON Schema 跟 JSON 是一對一的,如果我們需要比較兩個不同 JSON 的同一個字段,它同樣無能為力。這就引出了我們需要的第二個工具 —— JSONPath。
JSONPath 是一個 JSON 的資訊抽取工具,可以從 JSON 資料中抽取指定特定的值、對象或者數組,以及進行過濾、排序和聚合等操作。而 JSONPath 隻是一個 JSON 字段的提取工具,要利用它來實作一個斷言判斷還需要進一步封裝。
在這裡我們用一個 JSONPath 表達式來表示一個斷言,下面是一些簡單的使用示例:
// 校驗updateTime > createTime
$.updateTime > $.createTime
// 傳回的bookId必須為某個固定值
$.bookId == ["123456"]
// datas數組不能為空
$.datas.length > [0]
// datas數組中必須包含某本書,且價格要大于0
$.datas[?(@.bookId=='123456')] > [0]
值得注意的是,JSON Schema 和 JSON Path 斷言校驗并非二選一,既可以同時校驗,也可以根據場景選擇任意一種校驗方式。與此同時,如果項目前後端互動的協定是 XML、 proto 或者其他協定,可以将其統一轉為 JSON 格式,JSON 更容易了解且工具鍊更多更成熟,否則我們将要為每一種序列化的協定都開發一套類似的工具,重複勞動。
2.6 變更系統接入與排程
在這裡,我們使用異步 MQ 去排程測試任務,它有三個主要的特點:
多觸發源 | 任意粒度 | 指定環境 |
支援變更系統、管理平台、例行任務排程等多個來源的任務觸發信号。 | 支援按全量用例排程、按變更子產品排程、按用例組排程、按單用例排程。 | 支援排程到現網環境和測試環境,甚至可以指定 IP 對某台機器定向測試。 |
03
自動化測試系統實作
在擁有了一個接口自動化測試平台之後,我們面臨一個新的問題:如何快速提升自動化測試的覆寫率?
這個問題有一個隐含的前提,我們需要一個可以衡量覆寫率的名額,接下來将介紹我們如何構造這個名額,并分享一些提升覆寫率的方案。
3.1 變更系統接入與排程
要衡量覆寫率,第一反應必然是基于前後端約定的協定進行分析。但是沿着這條思路去分析我們遇到了以下幾個難點:
|
3.2 整體流程
3.3 流量特征分析
一個 HTTP 請求,我們通常需要分析的是以下部分:請求方法、URL、請求包、傳回包。而結合我們的業務場景,我們還需要一些額外的資訊:使用者 ID、平台(安卓、IOS、網頁等)、用戶端版本号等。我們調研過一些流量采集分析并生成用例的系統,大多隻能對通用資訊進行分析,并不能很好的結合業務場景進行分析,拓展性不足。
我們有一個請求,其 url 參數為 listType=1&listMode=2、vid 為10000、平台為 android、版本号為7.2.0,其請求體如下:
{
"bookId":"12345",
"filterType":1,
"filterTags":["abc","def"],
"commOptions":{
"ops1":"testops1",
"ops2":"testops2"
}
}
其中 url 和 header 裡的參數都很容易解析,不再贅言,下面講一下 JSON 請求中的參數提取方法。這裡我們用 JSONPointer 來表示一個參數的路徑,作為這個參數的 key 值,那麼可以提取獲得以下參數:
// url 和 header 中提取的參數
listType=1
listMode=2
vid=10000
platform=android
appver=7.2.0
// JSON 中提取的參數
/bookId=12345
/filterType=1
/filterTags=["abc", "def"]
/commOptions/opts1=testops1
/commOptions/opts2=testops2
如此一來,參數的表現形式可以統一為 key-value 的形式,我們調研的工具也基本止步于此,接下來要麼是用正交計算用例的方式輔助人工編輯用例,要麼就是對大量流量生成的用例進行去重。
但這達不到我們預設的目标,我們不妨更進一步,通過大量的線上流量構造出接口參數的特征,在這裡我們提出一個定義,接口參數的特征包括五部分:
|
我們的工作主要集中在參數的可枚舉性分析,這也是參數分析的突破點。假設我們從線上對某個接口進行采樣,采樣條數為 1W 條,将得到以下的參數:
listType=[1, 2, 3, 4]
listMode=[1, 2]
vid=[10000, 10001, 10002, 10003, ...] // 3000+
platform=[android, ios, web]
appver=[7.2.0, 7.1.0, 7.3.0, ...] // 20
/bookId=[12345, 23456, 34567, 56779, ...] // 4000+
/filterType=[1, 2]
/filterTags=[abc, def, efg]
/commOptions/opts1=[testops1, testops1_]
/commOptions/opts2=[testops2]
有了以上提取到的參數枚舉值,我們設定一個合理的門檻值(比如30),就可以判斷哪些參數是可枚舉的,很明顯 vid 和 /bookId 并不是可枚舉的參數,在覆寫用例時不需要對這兩個參數進行覆寫。
在實踐中,我們發現固定門檻值并不能精準識别到有效的枚舉參數,門檻值需要跟随采樣的資料動态調整。不同接口請求量可能從幾十到幾十萬不等,如果一個接口請求條數隻有30條,每一個參數的枚舉值都小于設定的門檻值,所有參數都是有效參數,這不符合實際情況。是以門檻值要随着采樣條數的變化而變化,可以按請求數量階梯變化,也可以按請求數量成比例變化。對于特定參數,還要提供人工配置快速介入,指定參數是否可枚舉。
在我們知道哪些參數是可枚舉的有效參數後,接下來可以對參數的可組合性進行分析。實際上我們并不需要分析任意兩個參數兩兩是否可組合,基于線上流量去分析即可。我們簡單給一個例子:
listType=1&listMode=1&platform=android&appver=7.2.0
listType=1&listMode=1&platform=ios&appver=7.2.0
listType=1&listMode=1&platform=web&appver=7.2.0
listType=2&listMode=1&platform=android&appver=7.2.0
listType=2&listMode=1&platform=ios&appver=7.2.0
listType=2&listMode=1&platform=web&appver=7.2.0
listType=3&listMode=2&platform=android&appver=7.2.0
listType=3&listMode=2&platform=ios&appver=7.2.0
listType=3&listMode=2&platform=web&appver=7.2.0
那麼在覆寫用例時我們需要覆寫這9個組合,通過組合分析我們甚至可以發現線上是否有錯誤使用的參數組合,需求是否發生了變更産生了新的組合參數。
要提升覆寫率,本質上就是覆寫所有可枚舉參數的枚舉類型以及組合,這就是我們在上面提到過的覆寫率名額。有了這個名額,我們就可以對覆寫率提出以下計算公式:
全局覆寫率 = 已覆寫的接口數 / 全部接口數 * 100%
接口有效用例 = 全部可枚舉參數的可枚舉值 + 全部可枚舉參數的組合
接口覆寫率 = 已覆寫的有效用例數 / 接口有效用例數 * 100%
PS:當接口覆寫率達100%時視為接口已實作用例覆寫
3.4 用例生成
經過上面對流量的特征分析以及篩選,我們得到了一批有效流量,接下來就可以使用這些流量來自動化生成用例,其中最主要的工作是為用例生成校驗的 JSON Schema 規則。其生成過程如下圖所示:
如上圖所示,任何 JSON Schema 的生成工具所生成的 Schema 都不可能百分百滿足業務需求,我們仍然要根據業務場景對 Schema 進行微調。比如在搜尋場景下,我們用一個 results 數組來承載傳回結果,生成器生成的 Schema 隻約定了 results 字段必須要存在,并且字段類型為數組類型。如果有一天傳回了一個空的 results 數組,那麼預設生成的 Schema 是檢查不出這個問題的,我們可以為 results 數組增加 minItems = 1 的規則,要求 results 數組必須大于等于 1,下次校驗時遇到空數組就能夠告警出來。
同時,在用例執行時遇到校驗不通過的情況,我們也設計了一套自動化 promote 用例的流程,不需要手工對用例進行改動。其流程如下:
其中用例優化分為三種情況:
移除用例:用例已失效,直接删除用例; 替換用例:用例不符合預期,從線上根據同樣的參數選取請求重新生成一個用例; 優化 Schema:用例中某些字段并非必需字段,或者屬于預期内的變化(比如使用者的未購變已購導緻某些字段被替換)。 |
我們使用的 Schema 生成工具是 genson,它可以為一個 JSON 生成對應的 JSON Schema。這個工具有個很重要的特性:它是一個多輸入的 JSON Schema 生成工具,可以接收多個 JSON 或者 Schema 作為輸入參數,生成一個符合所有輸入要求的 Schema,這一點正是我們自動化的關鍵,使得我們不需要手動編輯校驗規則。下面簡單展示一下我們現在的系統是如何優化失敗用例的:
3.5 用例發現與補全
用例的自動化發現分為兩個離線任務:一個是新接口的發現,一個是新用例的發現。
新接口是指我們有新的功能上線,當線上有流量通路時,我們應該及時發現這個新的請求,并将這個請求納入我們的自動化測試管理範圍。
新用例是指通過對流量分析,發現了新加的可枚舉參數,或者之前用例未曾覆寫的參數組合,我們通過對比線上流量和已經采集落庫的用例進行 diff 分析,得到并生成新的用例。
下圖是對用例的自動化發現與補全的簡單示例:
3.6 流量特征應用
基于上面提到的流量特征分析以及用例生成,我們的用例個數從150+提升到8000+,實作了讀接口100%用例覆寫,覆寫率有了一個質的飛躍。
對于寫接口實作了覆寫率統計以及用例推薦,極大降低了在編輯用例時的心智負擔,不需要自己去構造參數以及周遊所有的參數組合,跟随着推薦的用例去補全即可。
同時針對我們前面提到的前後端協定分散在各個地方,且接口與文檔不一緻的問題,我們通過線上流量對請求參數和請求回包的 Schema 進行持續的疊代,然後再将 Schema 反向生成 JSON, 就可以得到一份最全、最新的接口協定,同時這份協定還可以提供給用戶端同學用來構造參數進行 mock 聯調。
04
總結
至此,我們已經完成了整個背景接口自動化測試系統的搭建,并完成了預設的全部目标:
|
對于舊用例系統上的資料,我們花費了将近兩周,将數千行測試代碼、将近一千條校驗規則全部遷移到新的自動化測試平台上,得到了150+的新用例,并且校驗的規則變成了150+的 JSON Schema,不需要維護任何一行代碼,就得到了比之前更完善的全字段校驗規則覆寫。
此外,我們通過用例發現和用例生成,生成了8000+的用例,實作了讀接口100%用例覆寫,并多次輔助發現線上異常資料問題,在使用者還未感覺前就已經将問題扼殺在搖籃之中。
筆者認為,本文最重要的并不是對各種工具的內建和使用,100% 的用例覆寫也并非本文的最終目标。各種開源和付費工具數不勝數,隻要舍得投入人力 100% 的用例覆寫也并非難事。本文真正重要的是提出了一種通用的測試架構架構,以及基于線上流量分析得到了一種測試覆寫率的度量方案。
秉持着這種思路,上文中我們提到的排程系統、用例執行 MQ、校驗工具、測試告警系統、流量采集系統、用例生成系統,都可以基于業務靈活調整,低成本實作大規模用例覆寫。以上是本次分享的全部内容,如果覺得内容有用,歡迎分享轉發。
作者:柯宗言
來源:微信公衆号:騰訊雲開發者
出處:https://mp.weixin.qq.com/s/sjIY4foW6SpRK9HectSzKg