一、背景
從入職到現在已經有3個多月了,從最初的對於電商廣告業務和算法測試的一頭霧水,到現在的漸漸了解,基本上已經可以勝任項目的常規質量保障任務。然而,目前的質量保障手段依然停留在手工測試層面,自動化相對來說依然是比較空白。
關於持續集成和自動化,早已不是新鮮的話題。但是搜了下內網上關於自動化的一些話題,發現最近的一些文章還是有不少關於自動化價值的討論,我覺得自動化測試其實跟任何一種其它測試類型(包括功能測試、異常測試、性能測試、穩定性測試等)一樣,它本身屬於測試范疇的一種。既然是測試,我們首先要明白的是“測試需求”是什么。盲目地為了自動化而自動化,導致自動化使用姿勢不當或者目標不明確,甚至不知道痛點在哪,是無法將自動化做好的。
之前在TesterHome發表過一篇關於自動化的文章《我對自動化測試的一些認識》,可能隨着當前行業測試技術的快速發展,有一些技術手段已經有些陳舊,但是其中的大部分觀點至少到目前為止與當前自己的思想還是比較一致的。歡迎有興趣的測試同學一起探討,對不足之處進行指正批評。
二、算法測試的痛點
目前我負責的是廣告算法的質量保障工作,測試開發比將近1:20,按目前的情況來看,開發數量還有增大的趨勢。整個團隊並行在推進的項目可能在10個左右,總體來說測試資源非常緊張。
一方面,作為一個測試開發同學,在兼顧業務保障的同時,往往還需要保障多個組內外平台及工具的開發或者其他流程溝通推動的相關工作。另一方面,我們的開發同學其實都有較高的質量意識,有強烈的自測訴求,但是缺乏高效的自測手段。
因此,如何將項目測試過程固化沉淀、形成自動化保障體系,賦能開發自測,釋放測試自己的資源,是當前不得不進行的一個事情。
三、算法業務特點
1. 與網站應用/工程測試的比較
算法因為其自身的特殊性,大部分廣告算法實際上是對已有算法的改進和結合,涉及的技術包括點擊率預估、搜索、推薦、圖、NLP、圖像等。以搜索廣告為例,在流量變現方面,它的主要優化方向集中在兩個方面:排序(點擊率預估、出價優化)和匹配(召回、相關性)。從工程的角度來看,大部分項目對工程結果的影響是廣告商品的排序不同或是召回商品的內容和數量變化。不同於傳統的網站應用/工程測試(可能會有不斷的新功能的產出),相對來說,如果拋開算法效果測試來看,在功能層面,算法的工程結果是比較穩定的。
2. 與傳統接口測試的比較
算法的開發主要包含兩部分:離線任務開發和在線工程開發。離線任務開發包括數據分析、業務建模、特征提取、模型訓練、模型調參等。在線工程開發主要是將算法模型服務化,以接口的形式提供給外部去調用。從集成測試的角度來看,算法的功能測試可以歸為“接口測試”的范疇。
傳統的接口測試,除了校驗接口的返回狀態碼是否正確外,比較容易根據業務的處理邏輯對返回的結果字段進行校驗。比如查詢類的get接口,我們可以通過接口的返回結果與數據庫的結果進行比較;比如提交類的post接口,我們也可以通過調用接口,根據傳入的參數的不同,結合對系統的不同處理邏輯的了解以及數據庫得到一個確定的預期返回。
與之相對的,算法的接口沒有一個確定的預期。同一個接口,傳入完全相同的參數,在不同的時間段調用可能會返回不同的結果。因為很多時候,算法的接口返回是一個概率值,如“某個商品被某個用戶點擊的概率”,影響算法返回結果的因素,除了服務代碼之外,還包括索引的版本、特征詞典的版本。特征詞典的版本往往是日更新,而索引包含日級別的全量更新和實時的增量更新。雖然算法的功能相對穩定,但是算法的不斷調整,實際上是對同一批接口的不斷調整。
3. 當前的主要測試手段
目前算法工程質量的保障,主要還是集中在在線部分的測試。離線部分因為其時效性,往往會有T-1、T-2的延遲,更多的采用監控保證其質量。而對於在線部分的測試,主要的保障方式包括:
- CodeReview
- 功能驗證
- 線上請求日志回放
- 性能測試
- 穩定性測試
線上請求日志回放主要是基於線上的access_log日志,分別在新版本的環境和base環境進行請求回放,比較返回結果各個字段值的差異。是一種對功能驗證的補充,避免人為地構造數據可能無法覆蓋線上的所有場景。
四、算法自動化測試的挑戰
前面說了,當前算法自動化測試的目標主要是想解決工程測試方面的效率問題,釋放測試在工程質量保障方面的人力投入,因此本文主要是針對算法的“工程”測試自動化方面的一些思考。
理想的自動化測試鏈路應該是:
- 代碼提交
- 靜態代碼檢查
- 單元測試
- 代碼打包
- 詞典更新
- 測試環境部署
- 索引更新
- 功能冒煙回歸
- 線上日志回放
- 變更代碼覆蓋率統計
- 模塊壓測
- 集成壓測
- 測試報告
整條鏈路的回歸時間控制在一個小時以內,過程中既有中間測試結果的即時透出,也有最終報告的展現。下面是對於自動化測試鏈路的各個環節存在的一些挑戰的思考:
1. 代碼提交和管理
目前的開發代碼大部分都是用git去管理,但是並沒有一個明確且嚴格遵守的代碼分支管理策略,且沒有打Tag的習慣。算法和引擎開發同學有不同的開發習慣:
- 算法的同學開發習慣是在線上分支master上拉一個feature分支,用於新版本的開發,然后待feature全量發布上線后再將feature合回master。
- 引擎的同學開發習慣是直接將代碼合到master上提測。
從測試的角度來看,這兩種都存在一些問題:
- 第一種方式,雖然保證了master一直是穩定可用的版本,但是當有多個同學並行在此項目上開發多個feature時候,在多個feature前后相繼需要發布的時候,可能都是基於舊的master分支拉出的代碼,如何相互合並是一個問題。且當feature分支上線后,再合並到master,如果出現conflict,無法保證解決conflict后的master代碼與線上的代碼一致。
- 第二種方式,能保證提測的代碼是合並conflict后的最新代碼,同時也是測試通過后上線的真實代碼。但是會影響到master分支的穩定性,當線上出現一些問題,需要緊急回滾的時候可能會出現問題。
在分支管理上不妨可以參考一些優秀開源項目(如 apache/spark、apache/flink、tensorflow/tensorflow等)的普遍做法:
- 對於只有一個大版本的代碼,只維護一條master分支。通常大部分項目只有一個大版本,一些特殊的項目可能會存在多個大版本,如SP有3.0版本和2.0版本在線上同時使用,則相應維護兩個分支。
- 開發時,從master拉代碼到本地進行開發,本地自測完成后合代碼進master,及時解決沖突。
- 提測時,以tag的方式提測。比如目前要上線的RS服務版本為v1.0,則第一次提測在master分支上打tag名v1.0.1。如果測試不通過,修改代碼后重新提測,打tag名v1.0.2,依次類推。
- 最終測試通過后,打一個沒有后綴的tag:v1.0,表明是測試通過的可用版本。
- 最終上線基於tag:v1.0進行發布。
- 當線上驗證通過,且運行穩定后,可以在上線一周后將v1.0.1、v1.0.2……等過程中的提測tag刪除,只保留最后上線的穩定版本tag:v1.0。
- 用同樣的辦法上線v2.0時,如果發生線上問題,需要回滾,可以立即回滾到tag:v1.0。
2. 靜態代碼檢查
靜態代碼檢查是一項低投入高回報的工程,從集團推出Java開發規約可見其重要性。“低投入”是因為只要一次配置,可以無限次的檢查。“高回報”是它不僅可以幫我們糾正代碼書寫規范的一些問題,還可以發現一些諸如“空指針異常”、“I/O及數據庫連接資源使用后未關閉”、“冗余變量”等一些重大的工程風險。
對於Java,有集團規約插件、PMD、CheckStyle、FindBugs等可以很方便地使用,而算法的開發則大多是使用C++,相應的靜態代碼檢查工具還需要調研。目前了解到的有cppcheck、clint等。
3. 單元測試
目前RS服務已經使用了Google C++單元測試框架“Gtest”進行單元測試的編寫,但是還處於剛剛起步狀態,這部分需要去推動開發好好地補充測試用例,給出單元測試的覆蓋情況統計,不讓單測僅僅是流於形式。后續需要將單測的通過率和覆蓋率數據透出到報告中。
4. 算法打包部署
不同於Java工程,基於Maven或Gradle,可以通過命令行很方便地實現整個工程“一鍵編譯打包”,而且目前集團的Java應用大都已經可以基於Aone平台很方便地發布。而算法的發布因為其索引和詞典的特殊存在,無法通過Aone很好地集成。算法包的發布包含三塊內容:so文件(代碼編譯后的結果)、conf(配置)、data(詞典),過程通常包含RPM打包和詞典更新兩個步驟,其中RPM包會包含so文件、conf和部分data。目前不同算法模塊發布的平台各異,包括suez、drogo、autoweb等等,如何去規整並實現測試環境的自動發布是一項挑戰。同時,在環境部署完畢后,我們還要保證新版本環境和base環境索引的一致性,或者當索引的構建也發生改變時,需要保證構建索引的商品庫的一致性。
5. 功能冒煙回歸
這一塊需要對每個算法服務的使用場景進行具體的分析和細致的梳理,比如RS服務,它有來自bp、rsproxy、dump、rspe等不同的調用方的請求,每個調用方的可能的使用場景都需要一一梳理,從而提取出必要的冒煙接口測試用例。前面提到過算法接口每次調用的返回值可能會變化,
因此這部分用例的意義在於檢測服務接口是否針對各個場景有正常的返回,返回結果的各個字段的值是否在一個合理范圍內。
6. 線上日志回放
相比人為地構造數據,和AB切流實驗,線上日志回放是一項相對覆蓋面廣且高效低成本的方式,通過自動化地回放大量的日志請求,我們可以發現新的算法版本是否對某一些不該產生變化的結果字段產生了影響,也可以發現一部分算法的效果問題:如出價優化時,整體價格是調高了還是調低了。
7. (變更)代碼覆蓋率統計
Java中有成熟的Jacoco可以用於工程覆蓋率(深入到行、方法、類等不同級別)的統計,結合git的diff日志,還可以去挖掘工程兩個不同版本間變化的代碼的覆蓋情況。而C++的覆蓋率統計方式仍需要調研。
8. 性能壓測
性能壓測包括模塊壓測和集成壓測。模塊壓測指的是如RS這樣單個服務的壓測,而集成壓測是從ha3端或者SP端去看整體性能的情況。通常,性能的測試都是需要有一定經驗的測試人員基於測試分析來制定詳細的壓測方案並予以執行和結構分析。但是,之前提過,算法的項目發布有很大的共性,正是基於這些共性,使得自動化壓測成為了可能。要實現自動化地壓測,除了要編寫通用的壓測腳本,還要有穩定的施壓環境作為持續集成的節點機器。同時還需要去實現自動化地采集被壓環境的系統性能變化數據(如cpu、內存、磁盤i/o、網卡性能等)。
9. 報告生成
要實現最終報告的自動化生成,需要涉及到整條鏈路的各個環節數據的自動采集,以及各項數據呈現和報表的展示,透出和提煉出對研發過程有幫助的數據,給出相應地測試結論。
五、總結
以上是個人對於算法工程質量保障的一些小小的想法,目的是將自己從“項目輪流轉”的現狀中解放出來。整條鏈路看似冗長,但是羅馬不是一天建成的,我相信只要朝着正確的方向前進,自動化的效果會一步步地顯現出來。