演算法自動化測試的挑戰與思考
一、背景
從入職到現在已經有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分支的穩定性,當線上出現一些問題,需要緊急回滾的時候可能會出現問題。
- 對於只有一個大版本的程式碼,只維護一條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. 報告生成
要實現最終報告的自動化生成,需要涉及到整條鏈路的各個環節資料的自動採集,以及各項資料呈現和報表的展示,透出和提煉出對研發過程有幫助的資料,給出相應地測試結論。
五、總結
以上是個人對於演算法工程質量保障的一些小小的想法,目的是將自己從“專案輪流轉”的現狀中解放出來。整條鏈路看似冗長,但是羅馬不是一天建成的,我相信只要朝著正確的方向前進,自動化的效果會一步步地顯現出來。