1. 程式人生 > >Mock+Proxy在SDK項目的自己主動化測試實戰

Mock+Proxy在SDK項目的自己主動化測試實戰

卸載 要求 center 臨時 hub data lease tin 行為

項目背景


廣告SDK項目是為應用程序APP開發者提供移動廣告平臺接入的API程序集合,其形態就是一個植入宿主APP的jar包。提供的功能主要有以下幾點:
- 為APP請求廣告內容
- 用戶行為打點
- 錯誤日誌打點
- 反作弊

團隊現狀


在項目推進的過程中。逐漸暴露了一些問題:
1. 項目團隊分為上海團隊(服務端)和北京團隊(client),因為信息同步,人力資源等其它原因。服務端與client的開發進度非常難保持同步,經常出現client等著和服務端聯調的情況
2. 接口文檔不穩定,理解有偏差
3. 協議變化頻繁,消息不同步
4. 缺少服務端測試環境,可模擬的真實廣告內容太少
5. 協議字段太多,傳統的測試用例設計方法easy出現遺漏,尤其是異常情況處理。測試完畢以後測試人員對字段覆蓋率沒有信心

協議字段演示樣例圖

{
     "ads": [{
        "action": {  "path": "" },
        "adw":920, 
        "adh":900, 
        "template": "",
        "action_type": 2,
        "adid": "67346778",
        "adm": "",
        "adm_type": 0,
        "deep_link": "",
        "impid": "nXcM_kqBGqL=",
        "tk_act": [""
]
, "tk_imp":[ ""], "tk_ad_close": [""], "tk_clk": [""], "tk_dl_begin": ["" ], "tk_dl_btn": [ ""], "tk_dl_done": [""], "tk_dp_suc": [], "tk_ins": [ ""], "tk_open": [""] }]
, "errno": "0" }
  1. 測試用例設計極easy受需求影響,更新起來非常麻煩,成本非常高
  2. 手工測試方法運行效率低,且easy漏測

手動測試過程示意圖
技術分享圖片

分析&思路


上述幾個問題,當中1、2、3都會對我們的測試工作產生影響。可是屬於項目管理範疇。不在本文討論範圍內。那麽針對4、5、6、7幾個問題,應該怎樣解決呢?
首先分析一下問題:

Q: 缺少服務端測試環境,可模擬的真實廣告內容太少
A: 因為服務端團隊在人力上的不足,無法為我們提供測試環境。通過溝通協商,方法暫定為由服務端同事預先配置好線上廣告物料,即固定的線上廣告資源。能夠覆蓋提測的廣告類型,在服務端完畢功能邏輯之前,先利用mock方式測試client的功能邏輯以及展示,此時client和服務端後臺無需交互。

Q: 協議字段太多。傳統的測試用例設計方法easy出現遺漏,尤其是異常情況處理
A: 制定一個可靠的測試用例設計策略,以最少的case覆蓋最多的情況。

Q:測試用例設計極easy受需求影響。變更起來非常麻煩,成本非常高
A: 對測試用例進行拆分,分為正常返回情況和異常處理兩部分。

正常的處理包含系統環境、網絡切換、下載、輪播、緩存、正常打點、安裝卸載、UI檢查等等須要人工檢查的情況,因此這部分我們先梳理checklist,先組內review,再約產品和研發一起review。確保需求的完整性,另外開發過程中的需求變更是不可避免的,對於需求的變化要做到實時更新case。這部分case覆蓋的點要足夠全,而文字描寫敘述要盡量的精簡。確保更新起來能高速響應節奏的變化。而異常的部分。我們的做法是批量自己主動化生成case。生成策略會在以下具體描寫敘述。

Q:手工測試方法效率低,且easy漏測
A: 正常的功能我們通過手工測試的方法覆蓋,而對於client拿到的異常情況的error code要有全量的覆蓋,比方我們的錯誤代碼約定了21種,那麽針對全部可能出現的錯誤代碼都要想辦法觸發,這一部分工作希望從case生成到用例運行能100%的自己主動化實現。

調研過程


有了解決思路,那麽須要想辦法把想法落地。我們提煉出幾個須要攻克的技術難點:

  • 難點一:mock框架選型
    做過單元測試的同學應該了解“樁(stub)測試”。即通過hard code方式驗證函數的輸入輸出是否準確和健壯,而mock測試和樁測試相似,功能要更加豐富一些,能夠模擬產品交互環節中的部分場景,換句話說,能夠讓測試工作提前介入研發流程中。

    多用於須要聯調的環節,比方支付場景,購買流程,第三方插件調用等等業務。之前我們採用的Fiddler重定向請求結果到本地文件的方式模擬服務端的response來欺騙client,也能夠理解為mock測試。
    最初我們計劃自己寫一個proxy server監聽指定port,截取全部的http/https請求。再替換response內容完畢mock測試。後來一次偶然的機會接觸了阿裏開源出來的anyproxy(http://anyproxy.io/cn/),了解了一下該工具,發現這款工具剛好滿足了我們的幾個需求:

    • 代碼開源
    • 規則可定制
    • 支持https可視化
    • 易部署、學習成本低
    • UI可視化。相似fiddler

    技術分享圖片

    實際使用截圖(我們對response展示做了點優化):
    技術分享圖片

  • 難點二:可靠的測試用例設計策略
    在討論接口測試用例設計之前,我們須要預先圈定一個思考範圍,以免過度的思維發散。

    結合我們的業務特征,因為SDK的功能大部分是單接口。少部分是關聯接口。因此我們的設計基於單接口而非單個業務場景
    接口的測試用例設計有別於其它測試用例,其業務邏輯主要體如今字段的取值上,每一個取值體現了一種業務邏輯,我們做了一些調研。學習了其它業務團隊的接口測試用例寫法。發現測試人員喜歡這樣設計case:
    技術分享圖片

這種case無疑是工整、直觀的,可讀性比較強。非常方便的復制粘貼,再通過改動當中的一個或者幾個值,形成一個龐大的二維數組。
看到這個表格。一些熟練的測試project師會立刻聯想到邊界值、等價類設計、正交試驗法等。

然而要想保證每一個場景都被完整的覆蓋。理論上我們須要測試全部字段的笛卡爾積。這種方式能夠保證不論什麽取值都會被覆蓋到。可是當字段比較多的時候。測試用例的數量會呈爆炸式的增長。毫無疑問這種方式是不可行的。我們須要一個算法,能做到以下幾點:
1、 以最少的組合覆蓋盡可能多的場景
2 、覆蓋全部字段的全部取值
3 、有統計學支撐,生成的數據有規律可循

有了需求,我們開始進行了可行性方案的研究,秉承不反復造輪子的理念,我們查閱了國內外非常多的資料,逐漸的縮小了範圍,在說出解決方式之前,先給大家簡介兩個重要的算法:“ OATS(Orthogonal Array Testing Strategy)”和“Pairwise/All-Pairs Testing”。簡稱“正交表法”和“配對測試法”。


正交表法

正交表法有兩個重要的特性,大家嘗試著理解一下:
1.每列中不同數字出現的次數相等

備註:這一特點表明每一個因素的每一個水平與其它因素的每一個水平參與試驗的幾率是全然同樣的。從而保證了在各個水平中最大限度地排除了其它因素水平的幹擾,能有效地比較試驗結果並找出最優的試驗條件。

2.在隨意兩列其橫向組成的數字對中,每種數字對出現的次數相等

備註:這個特點保證了試驗點均勻地分散在因素與水平的全然組合之中,因此具有非常強的代表性。

舉個樣例:有三個字段,每一個字段能夠取三個值,設字段表現為A(A1,A2,A3)、B(B1,B2,B3)、C(C1,C2,C3),能夠組成的集合恰好能夠表現為一個三維空間圖,例如以下圖所看到的:
技術分享圖片

圖中的正方體中每一個字段的每一個水平代表的是一個面。共九個面,隨意兩個字段的水平之間都存在交點,共27(3x3x3)個,這就是笛卡爾積。依照兩大特性設計出的正交表如右圖所看到的,試驗點用⊙表示。我們看到,在9個平面中每一個平面上都恰好有三個點而每一個平面的每行每列都有一個點,並且僅僅有一個點,總共九個點。

這種試驗方案。試驗點的分布非常均勻。試驗次數也不多。
國外有一個站點能查詢正交表的結果案例:http://www.york.ac.uk/depts/maths/tables/orthogonal.htm
技術分享圖片


配對測試法
配對測試法(Pairwise)是L. L. Thurstone( 1887 – 1955)在1927年首先提出來的。他是美國的一位心理統計學家。

Pairwise是基於數學統計和對傳統的正交分析法進行優化後得到的產物。

定義:Most field faults were caused by either incorrect single values or by an interaction of pairs of values.” If that’s generally correct, we ought to focus our testing on the risk of single-mode and double-mode faults. We can get excellent coverage by choosing tests such that 1) each state of each variable is tested, and 2) each variable in each of its states is tested in a pair with every other variable in each of its states. This is called pairwise testing or all-pairs testing.
大概意思是:缺陷往往是由一個參數或兩個參數的組合所導致的,那麽我們選擇比較好的測試組合的原則就是:
1)每一個因子的水平值都能被測試到。
2)隨意兩個因子的各個水平值組合都能被測試到,這就叫配對測試法。
參看:http://www.developsense.com/pairwiseTesting.html

Pairwise基於例如以下2個假設:
1. 每一個維度都是正交的。即每一個維度互相都沒有交集。
2. 依據數學統計分析。73%的缺陷(單因子是35%,雙因子是38%)是由單因子或2個因子相互作用產生的。19%的缺陷是由3個因子相互作用產生的。

因此。pairwise基於覆蓋全部2因子的交互作用產生的用例集合性價比最高而產生的。國外也有一份相似的數學統計:
技術分享圖片

我們通過一個訂飛機票的實際樣例來看一下,配對測試法是怎樣從笛卡爾積中提煉出局部最優解的。


依舊是三個字段的組合,各自是Destination(Canada, Mexico, USA),Class(Coach, Business Class, First Class), Seat Preference(Aisle, Window)。所相應的笛卡爾積共同擁有3x3x2=18中測試組合,例如以下表所看到的。
技術分享圖片

經過配對測試法篩選後,結果例如以下:
技術分享圖片

經過篩選以後,我們的測試用例變成了9條。case數量精簡了50%。簡單總結pairwise的篩選原理就是,發現兩兩配對在全集中有反復的就去掉當中之中的一個,這樣篩選也有副作用,每次篩選完了條數是固定的。可是結果卻不盡同樣。可是通過上面的介紹我們不難比較出兩種算法的差異。

說了那麽多。再回到我們之前提到的設計策略幾個需求,能夠覺得pairwise算法的特征基本滿足了我們的需求。

  • 難點三:測試用例自己主動化生成
    確定了用例設計的算法策略後,我們信心十足的準備開始設計我們的response返回值case了,我們套用文獻中的排列分布方式應用到實際接口json中,悲傷的發現我們要組合的字段不是3個。而是20-35個左右,假設通過人工的方式來進行case設計的話,就算僅僅考慮最多兩個字段的值發生變化。數量也是非常驚人的,WWWWWWhat???
    技術分享圖片

本著“偷懶是人類進步的第一動力”的想法,我們自然不會前功盡棄,自己主動化測試是我們的必選之路,接下來要做的就是調研眼下已經存在的基於pairwise算法的工具有哪些,以下是經過調研後得到的工具列表。
技術分享圖片

基於pairwise算法的工具如此之多,那麽同樣模型設定下產生的結果是否存在差異呢?我們看一下這張圖:
技術分享圖片

綜合比較各工具產生的數據結果後,我們能夠發現不同工具之間的結果差異並不大,基本上能夠滿足我們現有的需求。經過一番討論後,我們決定採用微軟的PICT(https://github.com/Microsoft/pict)作為case生成工具。原因有幾點:
1. 代碼開源可擴展
2. 源代碼依舊在維護。貢獻比較活躍
3. 產品成熟,語法豐富
4. 基於貪心算法。局部最優解


難點四:測試用例生成的設計
用例生成過程分為五個步驟:
技術分享圖片

1. 準備字段值
依據Wiki的接口文檔,測試人員理清字段結構。字段類型,字段取值範圍後,結合傳統的case設計理念。構造出每一個字段的賦值。存放到整理好的excel中。大概是這種:
技術分享圖片

有的同學可能會問:你這樣整理也挺麻煩。感覺人工也沒省多少事兒。這樣設計的優點是,當字段發生變化的時候。僅僅須要從源頭改動字段屬性、值、層級、甚至刪除,後面整個流程中的case都會統一生效,字段集中管理。牽一發而動全身。和UI自己主動化用到的page-object設計相似。

2. 構建模型
有了面粉了。還須要加工一下才幹變成我們想要的面包,我們須要把準備的數據整理成能夠批量生成的可識別文件,即模型文件。PICT的模型文件有自己的格式。相似這樣:

參數定義
[子模型定義]
[約束定義]

舉個樣例,前面提到的訂票系統的樣例加工成模型文件是這種。後面會給大家介紹語法含義:

Destination:  Canada, Mexico, USA
Class:  Coach, Business Class, First Class
Seat Preference:  Aisle, Window
{Destination, Class} @ 2

3. 生成Case
通過pairwise工具將模型文件組裝成我們想要的case,那麽上面的模型生成的case會是這樣:

Destination Class Seat Preference
Canada First Class Window
USA First Class Aisle
Canada Coach Aisle
USA Business Class Window
Mexico Business Class Aisle
Canada Business Class Aisle
Mexico First Class Window
USA Coach Window
Mexico Coach Window

註:選擇強度為2,因此上面的矩陣是兩兩變化的。

如前面所說,這裏生成的矩陣內容不是固定的!

4. 準備期望結果
輸入數據已經準備好了,那麽相對於case而言,是不是還缺一個期望結果呢?在這裏我們碰到了一個難題,可能做過case自己主動生成的同學都會遇到的,就是生成排列組合是非常easy的,怎樣讓這些組合變得有意義,體如今我們的期望結果上。那麽一次性生成如此多的case,怎樣讓輸入值和期望結果對號入座呢?
我們的做法是:拆分了postive testing 和 negative testing(合法輸入測試和非法輸入測試或負面測試)。通過整理接口case我們不難發現。合法輸入的case事實上占整個case的比重並不大。工作量比較大的是各種參數的異常數據輸入,相應的會產生error code或二次請求。僅僅須要我們在整理數據的時候給出相應的error code就可以,如圖所看到的:
技術分享圖片

有的同學會問:我們協議還不穩定,error code也不明白,有些輸入也不知道相應什麽error code,怎麽破?別急。後面告訴大家。

5. 生成mock數據
完畢了以上準備工作以後,剩下的就是生成我們mock須要的response json數據了。

解析Wiki協議中的json模版,給相應的json字段賦上生成的值。這裏須要寫一段代碼來完畢,在此不做贅述。

番外篇:工具的二次開發
在使用過程中。我們發現工具PICT不能滿足業務場景的復雜度要求,主要有兩點:
- 異常輸入測試的時候,不能同一時候輸入多個異常值
在case設計中多個異常值輸入是非經常見的測試場景。盡管pict提供負面測試(negative testing)功能。即假設模型文件裏,有值被標記為異常值(默認的異常值標識符為“~”),則case中會隨機出現一個異常輸入的值,可是PICT限制每一個case僅僅能有一個異常值存在,原因是多數異常值的組合盡管可能會引發問題,可是代碼在catch了一個異常值造成的異常後,不會再去處理還有一個異常值。


先通過一個演示樣例來感受一下pict的負面測試。演示樣例模型文件例如以下:

Destination:      Canada, Mexico, USA, ~Japan
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, ~Door

產生的case例如以下:
技術分享圖片

通過上圖能夠看出,PICT同一時候保證了正常值的組合,也保證了異常值的組合,可是我們不難發現,每一個case僅僅會出現一個異常值,那麽 ~Japan,First Class, ~Door的case就會遺漏,顯然case覆蓋率不夠,不能滿足我們的需求。
針對這個問題,在對PICT的源代碼進行了具體的解讀後,我們對代碼進行了二次開發,擴展了負面測試的覆蓋範圍。徹底攻克了這個問題,改動後的模型文件例如以下:

Destination:      Canada, Mexico, USA, ~Japan
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, ~Door
~{Destination, Seat Preference}  
//添加一行公式,在模型文件裏指定了Destination和Seat Preference兩個字段能夠進行異常值組合,數量不限。

擴展後的case生成是這種:
技術分享圖片

  • 正則表達式過於簡單。不支持復雜的語句
    PICT支持IF[ ] THEN[ ]格式的約束規則。可是約束規則中LIKEkeyword的通配符操作僅僅支持*和?(分別表示隨意多個字符和隨意一個字符)。顯然簡單的通配符操作限制了約束規則的表達能力。

    因此,我們在原有的基礎上,引入C++的regex庫支持正則表達式,改動後支持了更豐富的正則表達式。


    例如以下演示樣例中,添加一條規則。假設Destination字段為數字類型(”\d”),那麽Seat Preference字段也為數字類型。

Destination:      Canada, Mexico, USA, 3
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, 4
If [Destination] like "\d" then [Seat Preference] like "\d";

生成的case例如以下圖:
技術分享圖片

有了強大的正則表達式,再加上多異常組合輸入的支持。眼下已經全然能覆蓋我們須要的不論什麽場景,向開源致敬!

結構流程設計


在整個測試過程中,我們唯一須要人工介入的就是字段值的賦值以及跟error code的相應關系設計,協議字段的取值會受業務影響,臨時無法通過自己主動化的方式來進行。流程如圖所看到的:
技術分享圖片

最初的版本號比較簡單,結構大概是這種:
技術分享圖片

原本的設想是想繞過廣告宿主直接調用SDK的API請求廣告,從而節省一部分時間,且更easy自己主動化,可是因為廣告SDK本身特殊的設計。這個想法無法實現,因此當時的設計是通過觸發APPbutton點擊發送request給SDK。再由SDK發送加工後的請求到proxy server。再經過mock server處理數據以後,返回給client來顯示廣告。在此過程中,徹底拋棄了Fiddler重定向的傳統做法。

技術分享圖片

在階段二我們攻克了幾個問題:
- 實現內部循環請求廣告,解決手工觸發請求的問題
- 監控APP自身出現的crash和ANR
- 解決case失敗後能夠rerun,
- 解決中途運行中斷能夠rerun
- 因為一些廣告請求失敗會觸發二次打點請求。因此我們須要把相應的case和打點請求結果匹配上,我們通過在request中添加caseID來解決該問題
- 解決多種廣告類型不能連續一次性運行完,須要切換場景的問題
- 當出現期望結果與實際結果不符時,自己主動又一次運行該case 若幹次(可配置),假設一直失敗計為case失敗

技術分享圖片

該階段非常明顯,我們遇到了運行速度的問題。因為廣告種類的添加。我們的case達到了3400余條,因為還須要兼顧廣告渲染完畢後的打點結果檢查,運行全量case耗時達到了3個小時多。偏離了我們mock測試的初衷。因此如上圖所看到的。我們用到了分布式結構。mock server能夠通過client指紋信息來調度和發送任務給指定的手機,把case和設備緊密連接在一起,避免反復運行同樣的case。
另外我們把config、case、期望結果、運行結果等諸多信息全部遷移到database中,一方面解決頻繁的文件讀取問題,還有一方面攻克了分布式調度跨server的問題。

截止眼下,我們的測試數據是這種:

Case總數 發現BUG 遺漏的異常處理
3498條 77個 331個

前面提到的問題,假設error code尚未明白,case應該怎樣匹配呢?我們的做法是設定一個基準error code,當運行結果出來後,會有實際結果與期望結果不符的case。拿去和開發對一下就能夠,而調整error code期望結果以後。又一次生成case也僅僅只是分分鐘的事。

我們的運行時間:
技術分享圖片

收益:協議變更時,僅僅須要改動最開始的存放字段值的文件,興許的建模、case生成、期望結果填充、運行測試用例全部自己主動完畢。測試人員查看運行結果就可以

技術分享圖片

技術分享圖片

問題總結


因為我們也是第一次在mock測試中實踐自己主動化構造測試數據,包含用到的pairwise模型的合理性和準確性,都屬於初次嘗試,眼下在項目中取得了一定的效果,可是也遇到了非常多的困難,個中酸楚不足以一一道來,同一時候架構和流程還有非常多優化空間。

眼下依舊留存的問題包含:
- 自己主動生成case中,int、string、date等字段提取公共case。比方特殊字符、空、null、js等常規異常檢查
- 更復雜的邏輯,比方關聯字段依賴、加密字段、隨機數、MD5、token等情況
- 非http(s)的自己定義協議
- 分布式調度的更大規模的使用
- SDK的自己主動化測試對於APP的強依賴關系
- 正常的功能測試驗證
- 業務邏輯產生的漏測率統計

諸如此類的問題還有非常多非常多。尤其是結合項目自身特點,就會更加復雜,希望能通過我們的探索之路給同行們很多其它的啟示。

Mock+Proxy在SDK項目的自己主動化測試實戰