天天看點

BUG越改越多?微信團隊用自動化測試化險為夷

導讀

作為背景開發 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 等,這些都是開箱即用的,但經過調研和評估,我們還是決定自己造一個輪子。考慮的點如下:

  • 測試工具的實作原理并不複雜,實作成本不高,維護難度不大;
  • 現有工具并不符合業務要求,例如自定義的排程方案,以及支援内部 RPC 架構;
  • 我們需要把自動化測試與現有的系統連接配接起來,比如上線系統,用例失敗告警系統,流量分析系統等;
  • 當我們需要一些非标準能力的時候,外部工具很難快速,甚至無法支援,拓展性弱;
  • 這個系統主要是為了覆寫背景接口測試,使用體驗上要更貼近背景同學的使用習慣,降低用例管理成本。

1.4 目标

結合我們遇到的痛點以及業務需求,自研的自動化測試系統應該具備以下的能力:

  • 它應該是跟實作語言無關的,甚至是無代碼的,消除不同程式設計語言和架構帶來的隔閡;
  • 編寫用例應該是純粹的,用例跟測試服務分離,變更用例不需要變更自動化測試服務;
  • 能夠支援場景測試(多個用例組成場景),且能支援用例間的變量引用;
  • 提供多種排程方案,可以按全量排程、按業務子產品排程、按用例組排程、按單個用例排程,充分滿足業務和調試需求;
  • 這個系統要支援同時管理 HTTP 和 RPC 用例,可以覆寫請求的上下遊鍊路;
  • 盡最大可能降低背景同學編寫用例的成本。

02

自動化測試系統實作

2.1 整體架構

BUG越改越多?微信團隊用自動化測試化險為夷

2.2 統一 HTTP 和 RPC 通路形式

HTTP 和 RPC 請求在形式上可以被統一起來,其描述形式如下:

HTTP通路方式:http://host:port/urlpath + reqbody

RPC通路方式:rpc://ip:port/method + reqbody

通過這種統一的描述形式,再結合我們的業務架構,就可以設計一種通用的通路方式。背景的系統架構如下圖所示:

BUG越改越多?微信團隊用自動化測試化險為夷

從 proxy 層往下,所有的調用都是一個個背景服務子產品,HTTP 通路的是邏輯層,RPC 通路的是服務層。那麼隻需要配置用例的歸屬子產品,通過子產品名 + Client 配置就可以對 HTTP 和 RPC 請求進行區分以及尋址。

從變更系統的角度來看,我們的上線變更也是按子產品來的。是以把用例歸屬到一個個具體的子產品,是最符合背景同學認知的做法。

是以我們通過配置子產品名這種統一的形式,為使用者提供了統一的管理方式,隻需要指定子產品名就可以任意通路 HTTP 或者 RPC 請求,其流程如下:

BUG越改越多?微信團隊用自動化測試化險為夷

在紅色虛線框的流程中,隻需要配置子產品名,就可以通過子產品名擷取到 RPC 服務的所有資訊,包括其接口定義、請求包定義、回包定義,這不是一種通用能力,需要業務基于系統架構以及線上環境去拓展,但這帶來了以下便利:

  • 可以支援任意的業務 RPC 架構,拓展性強;
  • 隻需要配置子產品名就可以通路所有的 RPC 請求,無需逐個手動上傳解析 proto 檔案,減少操作步驟;
  • 不需要關心 proto 的更新,實時拉取線上 proto 的資訊,協定永遠是最新版本。

這裡的統一包含兩部分:第一部分是通路形式的統一(子產品),降低了配置用例的成本;第二部分是資料的統一(JSON),它統一了對回包方式的校驗,降低了校驗成本。

2.3 接口參數傳遞(參數池構造)

很多業務場景的完成都是由多個接口組成的一條鍊路實作,而且這種鍊路型的自動化測試,通常會存在參數依賴關系,一個用例的入參,可能要依賴上遊響應回包的某個字段值,是以需要提取出來并傳遞給下一個接口。如下圖:

BUG越改越多?微信團隊用自動化測試化險為夷

其解決方案是,通過正則或者 JSON Extracor 等提取的結果作為變量,然後再傳遞給下遊用例使用,這也是很多測試工具使用的方式,但是維護起來不夠友善,仍有進一步優化的空間。

于是我們提出了參數池的概念,将每個用例可能用到的字段都放入一個池子裡,這個池子的元素是一個個 key-value。key 是我們要使用的變量,value 則是 key 對應的取值,值得注意的是,value 既可以是一個字面值,也可以是一個 JSONPointer 的路徑,這個路徑可以從響應回包中提取變量值。

在這種方式下,不同用例間的參數依賴不再是從上一個“傳遞”到下一個,而變成了一個随取随用的池子,是以我們把它稱為參數池。同時我們通過自定義的文法,實作了一個簡單的模闆引擎,将我們引用的變量替換為池子裡的 value 值。參數池構造以及使用圖示如下:

BUG越改越多?微信團隊用自動化測試化險為夷

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');
});
}           

這種校驗方式存在以下幾個問題:

  • 這是針對單個字段進行校驗,如果一個回包裡有幾十上百個字段,這種手工方式不可能實作全量字段校驗;
  • 編寫一個用例需要有 js 基礎,對其他程式設計語言的使用者不友好;
  • 斷言規則都是一條條散落在代碼檔案中,展示和管理有難度;
  • 調試需要變更測試服務,調試成本高。

現有架構的不便導緻了用例管理上的種種問題,而我們根據這些不便之處去反向思考,我們到底需要什麼樣的校驗方式,這種情況下我們找到了 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 的優點非常顯而易見:

  • 可讀性高,其結構跟 JSON 資料完全對應;
  • 所有規則都處在一個 Schema 中,管理和展示清晰易懂;
  • 它本身是一個 JSON,對于任何程式設計語言的使用者都沒有額外學習成本;
  • 此外,我們可以通過一個現有的 JSON 反向生成 JSON Schema,然後在這個 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 對某台機器定向測試。
BUG越改越多?微信團隊用自動化測試化險為夷

03

自動化測試系統實作

在擁有了一個接口自動化測試平台之後,我們面臨一個新的問題:如何快速提升自動化測試的覆寫率?

這個問題有一個隐含的前提,我們需要一個可以衡量覆寫率的名額,接下來将介紹我們如何構造這個名額,并分享一些提升覆寫率的方案。

3.1 變更系統接入與排程

要衡量覆寫率,第一反應必然是基于前後端約定的協定進行分析。但是沿着這條思路去分析我們遇到了以下幾個難點:

  • 協定管理不規範,散落在 git 文檔、yapi、wiki 等多處地方,且格式不統一;
  • 文檔落後于實際接口協定,且可靠性有待考究;
  • 協定參數并非都是正交的,使用協定計算出來的參數組合不符合實際情況;
  • 是以,使用前後端協定進行分析這條路是行不通的。是以我們打算從線上流量入手,對流量的參數特征進行分析,并使用線上流量來生成自動化測試用例。

3.2 整體流程

BUG越改越多?微信團隊用自動化測試化險為夷

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 規則。其生成過程如下圖所示:

BUG越改越多?微信團隊用自動化測試化險為夷

如上圖所示,任何 JSON Schema 的生成工具所生成的 Schema 都不可能百分百滿足業務需求,我們仍然要根據業務場景對 Schema 進行微調。比如在搜尋場景下,我們用一個 results 數組來承載傳回結果,生成器生成的 Schema 隻約定了 results 字段必須要存在,并且字段類型為數組類型。如果有一天傳回了一個空的 results 數組,那麼預設生成的 Schema 是檢查不出這個問題的,我們可以為 results 數組增加 minItems = 1 的規則,要求 results 數組必須大于等于 1,下次校驗時遇到空數組就能夠告警出來。

同時,在用例執行時遇到校驗不通過的情況,我們也設計了一套自動化 promote 用例的流程,不需要手工對用例進行改動。其流程如下:

BUG越改越多?微信團隊用自動化測試化險為夷

其中用例優化分為三種情況:

移除用例:用例已失效,直接删除用例;

替換用例:用例不符合預期,從線上根據同樣的參數選取請求重新生成一個用例;

優化 Schema:用例中某些字段并非必需字段,或者屬于預期内的變化(比如使用者的未購變已購導緻某些字段被替換)。

BUG越改越多?微信團隊用自動化測試化險為夷

我們使用的 Schema 生成工具是 genson,它可以為一個 JSON 生成對應的 JSON Schema。這個工具有個很重要的特性:它是一個多輸入的 JSON Schema 生成工具,可以接收多個 JSON 或者 Schema 作為輸入參數,生成一個符合所有輸入要求的 Schema,這一點正是我們自動化的關鍵,使得我們不需要手動編輯校驗規則。下面簡單展示一下我們現在的系統是如何優化失敗用例的:

BUG越改越多?微信團隊用自動化測試化險為夷

3.5 用例發現與補全

用例的自動化發現分為兩個離線任務:一個是新接口的發現,一個是新用例的發現。

新接口是指我們有新的功能上線,當線上有流量通路時,我們應該及時發現這個新的請求,并将這個請求納入我們的自動化測試管理範圍。

新用例是指通過對流量分析,發現了新加的可枚舉參數,或者之前用例未曾覆寫的參數組合,我們通過對比線上流量和已經采集落庫的用例進行 diff 分析,得到并生成新的用例。

下圖是對用例的自動化發現與補全的簡單示例:

BUG越改越多?微信團隊用自動化測試化險為夷

3.6 流量特征應用

基于上面提到的流量特征分析以及用例生成,我們的用例個數從150+提升到8000+,實作了讀接口100%用例覆寫,覆寫率有了一個質的飛躍。

對于寫接口實作了覆寫率統計以及用例推薦,極大降低了在編輯用例時的心智負擔,不需要自己去構造參數以及周遊所有的參數組合,跟随着推薦的用例去補全即可。

同時針對我們前面提到的前後端協定分散在各個地方,且接口與文檔不一緻的問題,我們通過線上流量對請求參數和請求回包的 Schema 進行持續的疊代,然後再将 Schema 反向生成 JSON, 就可以得到一份最全、最新的接口協定,同時這份協定還可以提供給用戶端同學用來構造參數進行 mock 聯調。

04

總結

至此,我們已經完成了整個背景接口自動化測試系統的搭建,并完成了預設的全部目标:

  • 內建 JSON Schema 和 JSONPath 這兩個元件,實作了一個無代碼以及用例跟測試服務分離的自動化測試系統;
  • 通過用例的組合以及參數池構造實作了場景測試和用例間變量引用;
  • 支援了多種定制化的排程方案,并接入到上線系統流程中;
  • 打通 HTTP 和 RPC 接口通路,結合業務架構極大降低了接入 RPC 用例的成本;
  • 通過用例自動化生成進一步降低用例管理成本,快速提高了自動化測試的覆寫率。

對于舊用例系統上的資料,我們花費了将近兩周,将數千行測試代碼、将近一千條校驗規則全部遷移到新的自動化測試平台上,得到了150+的新用例,并且校驗的規則變成了150+的 JSON Schema,不需要維護任何一行代碼,就得到了比之前更完善的全字段校驗規則覆寫。

此外,我們通過用例發現和用例生成,生成了8000+的用例,實作了讀接口100%用例覆寫,并多次輔助發現線上異常資料問題,在使用者還未感覺前就已經将問題扼殺在搖籃之中。

筆者認為,本文最重要的并不是對各種工具的內建和使用,100% 的用例覆寫也并非本文的最終目标。各種開源和付費工具數不勝數,隻要舍得投入人力 100% 的用例覆寫也并非難事。本文真正重要的是提出了一種通用的測試架構架構,以及基于線上流量分析得到了一種測試覆寫率的度量方案。

秉持着這種思路,上文中我們提到的排程系統、用例執行 MQ、校驗工具、測試告警系統、流量采集系統、用例生成系統,都可以基于業務靈活調整,低成本實作大規模用例覆寫。以上是本次分享的全部内容,如果覺得内容有用,歡迎分享轉發。

作者:柯宗言

來源:微信公衆号:騰訊雲開發者

出處:https://mp.weixin.qq.com/s/sjIY4foW6SpRK9HectSzKg

繼續閱讀