化測(cè)試平臺(tái)建設(shè)實(shí)戰(zhàn):從用例管理到報(bào)告通知的完整鏈路)
這兩年我看過(guò)不少團(tuán)隊(duì)在做自動(dòng)化測(cè)試平臺(tái)真正用起來(lái)并且越用越順的反而不多。大多數(shù)情況是測(cè)試腳本攢了一堆報(bào)告散落在各臺(tái)電腦上CI里掛幾個(gè)定時(shí)任務(wù)跑掛了靠群里有人喊一嗓子才知道。這其實(shí)不叫平臺(tái)頂多算一個(gè)“能定時(shí)執(zhí)行腳本的服務(wù)器”。我參與過(guò)一套內(nèi)部自動(dòng)化測(cè)試平臺(tái)的搭建設(shè)計(jì)從用例管理、任務(wù)調(diào)度、執(zhí)行環(huán)境到報(bào)告通知一條鏈路走通踩了不少坑也沉淀了一套相對(duì)可行的落地方法。這篇就是講平臺(tái)建設(shè)過(guò)程中那些真正讓人頭疼的問(wèn)題以及我們最后怎么解決的適合正在規(guī)劃或已經(jīng)搭了半套測(cè)試平臺(tái)的團(tuán)隊(duì)參考。1. 為什么我們需要一個(gè)真正的平臺(tái)而不僅僅是一堆腳本很多人一提自動(dòng)化測(cè)試第一反應(yīng)就是寫(xiě)腳本。腳本當(dāng)然重要但腳本解決的是“某個(gè)用例怎么跑”的問(wèn)題平臺(tái)解決的是“所有用例在哪里跑、什么時(shí)候跑、跑完怎么讓所有人看到”的問(wèn)題。先搞清楚這個(gè)邊界后面每一步動(dòng)作都不會(huì)走偏。1.1 腳本散落一地自動(dòng)化測(cè)試沒(méi)有形成資產(chǎn)團(tuán)隊(duì)里有幾個(gè)人各自負(fù)責(zé)幾條業(yè)務(wù)線的接口測(cè)試腳本默認(rèn)放在自己電腦上偶爾推進(jìn)代碼倉(cāng)庫(kù)卻不強(qiáng)制。于是出現(xiàn)了經(jīng)典的一幕A同學(xué)的用例要跑得先問(wèn)他借電腦B同學(xué)的腳本已經(jīng)三個(gè)月沒(méi)更新接口字段早變了跑一次紅一片C同學(xué)的報(bào)告存在本地HTML文件里每次都要手動(dòng)截圖發(fā)到溝通群里。剛?cè)サ臅r(shí)候我覺(jué)得這不是技術(shù)問(wèn)題是管理問(wèn)題。但深入一看根源還是沒(méi)有公共的承載入口。沒(méi)有統(tǒng)一平臺(tái)自動(dòng)化測(cè)試的成果就無(wú)法沉淀。用例的版本、執(zhí)行的歷史、失敗的趨勢(shì)這些關(guān)鍵信息分散在個(gè)人電腦里領(lǐng)導(dǎo)問(wèn)一句“這個(gè)季度接口自動(dòng)化覆蓋率怎么樣”沒(méi)人能給出準(zhǔn)確數(shù)字。這不是某個(gè)人的錯(cuò)而是工具鏈的結(jié)構(gòu)性缺失。1.2 自動(dòng)化測(cè)試的價(jià)值被鎖死在“執(zhí)行”這一步自動(dòng)化測(cè)試本來(lái)應(yīng)該形成閉環(huán)用例編寫(xiě)、定時(shí)執(zhí)行、結(jié)果分析、缺陷跟進(jìn)、質(zhì)量反饋。但腳本散落的時(shí)候環(huán)是斷的。定時(shí)執(zhí)行靠人肉操作結(jié)果分析靠肉眼盯屏幕質(zhì)量反饋靠口頭匯報(bào)整個(gè)鏈路長(zhǎng)、慢、全憑自覺(jué)。平臺(tái)真正解決的是把這個(gè)閉環(huán)從“人驅(qū)動(dòng)”變成“數(shù)據(jù)驅(qū)動(dòng)”用例統(tǒng)一入庫(kù)執(zhí)行統(tǒng)一調(diào)度結(jié)果統(tǒng)一匯總報(bào)告自動(dòng)推送。誰(shuí)寫(xiě)的用例、什么時(shí)候改的、這次跑完跟上次比是好了還是壞了全都有據(jù)可查。自動(dòng)化測(cè)試從個(gè)人的手藝活變成團(tuán)隊(duì)的基礎(chǔ)工程設(shè)施。這一點(diǎn)想明白了平臺(tái)的定位自然就清楚了它不是測(cè)試工具的堆砌而是質(zhì)量數(shù)據(jù)的集散地。2. 自研還是組裝三個(gè)方案對(duì)比后我們才拍板平臺(tái)建設(shè)第一步也是最容易吵起來(lái)的一步就是選型。我們當(dāng)時(shí)的候選方案有三個(gè)每個(gè)看著都像模像樣但真正落地起來(lái)差別很大。2.1 三個(gè)候選方案的輪廓第一個(gè)方向是“開(kāi)源CI服務(wù) 報(bào)告插件”的組合繼續(xù)用團(tuán)隊(duì)已有的開(kāi)源CI服務(wù)在上面配置自動(dòng)化測(cè)試任務(wù)再搭一個(gè)報(bào)告展示頁(yè)面把結(jié)果聚起來(lái)。優(yōu)點(diǎn)是上手快CI服務(wù)團(tuán)隊(duì)本來(lái)就在用不用額外引入系統(tǒng)缺點(diǎn)是CI服務(wù)畢竟不是為自動(dòng)化測(cè)試平臺(tái)設(shè)計(jì)的用例管理、環(huán)境管理、執(zhí)行機(jī)分配這些能力要么沒(méi)有要么要自己寫(xiě)一堆插件往里面貼。第二個(gè)方向是商業(yè)測(cè)試管理平臺(tái)。功能齊全開(kāi)箱即用界面也漂亮缺點(diǎn)同樣明顯貴而且封閉。我們有些用例需要自定義執(zhí)行環(huán)境有些需要跟內(nèi)部的質(zhì)量數(shù)據(jù)體系做深度對(duì)接商業(yè)平臺(tái)往往不給底層接口定制一次的成本比想象中高得多。第三個(gè)方向就是自建輕量平臺(tái)從用例管理到執(zhí)行調(diào)度全部自己做。聽(tīng)上去工作量最大但好處是可以完全按照?qǐng)F(tuán)隊(duì)的工作流來(lái)設(shè)計(jì)后續(xù)所有定制需求都不受制于人。2.2 我們最終拍板自研的理由對(duì)比做完之后我們其實(shí)沒(méi)有全盤(pán)否定前兩個(gè)方向而是做了組合開(kāi)源CI服務(wù)繼續(xù)保留作為通用流水線工具但自動(dòng)化測(cè)試相關(guān)的用例管理、執(zhí)行調(diào)度、報(bào)告展示單獨(dú)建了一套自研平臺(tái)。為什么這么分核心原因是自動(dòng)化測(cè)試平臺(tái)跟CI服務(wù)的管理模型不一樣。CI服務(wù)關(guān)注的是“代碼變更后怎么構(gòu)建、怎么測(cè)試、怎么發(fā)布”以構(gòu)建任務(wù)為中心而自動(dòng)化測(cè)試平臺(tái)關(guān)注的是“測(cè)試用例在這個(gè)版本的代碼上表現(xiàn)如何”以用例和版本為中心。把用例直接掛在CI任務(wù)下面短期能跑長(zhǎng)期會(huì)非常別扭用例一旦多起來(lái)任務(wù)的配置頁(yè)會(huì)爆炸誰(shuí)負(fù)責(zé)哪個(gè)用例、哪些用例要在同一個(gè)環(huán)境里跑、失敗要不要自動(dòng)重跑這些問(wèn)題在CI配置里根本寫(xiě)不清楚。自研平臺(tái)的邊界我們也畫(huà)得很清楚不做測(cè)試框架不重新發(fā)明斷言語(yǔ)法只做執(zhí)行管理和結(jié)果沉淀。用例腳本用什么框架寫(xiě)都行平臺(tái)負(fù)責(zé)把它接進(jìn)統(tǒng)一調(diào)度再把結(jié)果結(jié)構(gòu)化展示出來(lái)。這個(gè)邊界讓我們避免了自研平臺(tái)最常犯的錯(cuò)誤——試圖替代測(cè)試框架最后兩頭不靠。3. 平臺(tái)骨架從用例到報(bào)告的完整鏈路設(shè)計(jì)平臺(tái)建設(shè)最忌諱一上來(lái)就堆功能。我見(jiàn)過(guò)一些內(nèi)部平臺(tái)菜單欄密密麻麻十幾個(gè)真正有人用的只有兩三個(gè)。我們的做法是先把一條主干鏈路跑通用例上傳、觸發(fā)執(zhí)行、日志回傳、報(bào)告展示、消息通知。圍繞這條鏈路設(shè)計(jì)架構(gòu)再逐步補(bǔ)血。3.1 四層結(jié)構(gòu)和三條數(shù)據(jù)流整個(gè)平臺(tái)從邏輯上可以分成四層接入層、調(diào)度層、執(zhí)行層、存儲(chǔ)層。接入層負(fù)責(zé)接收用例上傳和觸發(fā)請(qǐng)求調(diào)度層負(fù)責(zé)任務(wù)排期、并發(fā)控制、失敗重試執(zhí)行層跑在若干執(zhí)行節(jié)點(diǎn)上負(fù)責(zé)真正運(yùn)行測(cè)試腳本存儲(chǔ)層保存用例元數(shù)據(jù)、執(zhí)行記錄、日志和報(bào)告附件。數(shù)據(jù)流有三條剛開(kāi)始很容易被忽略。第一條是用例流轉(zhuǎn)從上傳解壓到登記入庫(kù)再到環(huán)境部署第二條是任務(wù)流轉(zhuǎn)從觸發(fā)命令生成任務(wù)記錄再到調(diào)度器分配執(zhí)行節(jié)點(diǎn)第三條是結(jié)果流轉(zhuǎn)執(zhí)行器把日志和結(jié)果實(shí)時(shí)回傳報(bào)告服務(wù)歸一化落庫(kù)再推送給前端和消息通道。三條數(shù)據(jù)流各自獨(dú)立又通過(guò)任務(wù)ID串聯(lián)起來(lái)排查問(wèn)題的時(shí)候只要順著任務(wù)ID追就能定位到具體環(huán)節(jié)。3.2 用例管理讓腳本從“私有財(cái)產(chǎn)”變成公共資產(chǎn)用例管理模塊是第一站也是決定平臺(tái)能不能被團(tuán)隊(duì)接受的關(guān)鍵。我們當(dāng)時(shí)被問(wèn)到最多的問(wèn)題是“我把腳本傳上去還能不能在我自己電腦上跑”答案是能。平臺(tái)不是要沒(méi)收大家的腳本而是提供一份“已登記副本”。本地的隨便改平臺(tái)上的版本只通過(guò)顯式提交才會(huì)更新。具體實(shí)現(xiàn)上每份用例上傳之后平臺(tái)會(huì)生成唯一的用例編號(hào)并從壓縮包里解析出執(zhí)行入口和依賴描述文件。后端只做校驗(yàn)不做改寫(xiě)保證上傳的腳本和本地跑的是同一份。用例版本用遞增的數(shù)字維護(hù)每次更新都會(huì)保留上一版本執(zhí)行記錄里明確標(biāo)注用的是哪個(gè)版本。這一點(diǎn)之后幫了大忙很多偶發(fā)失敗追到最后才發(fā)現(xiàn)是腳本版本不一致有了版本記錄這種排查時(shí)間能砍掉一半。3.3 執(zhí)行引擎隊(duì)列、并行和重試的設(shè)計(jì)執(zhí)行引擎是整個(gè)平臺(tái)里最容易出問(wèn)題的地方。我們的設(shè)計(jì)很簡(jiǎn)單觸發(fā)請(qǐng)求先寫(xiě)成任務(wù)記錄狀態(tài)為“待執(zhí)行”調(diào)度器定期掃描待執(zhí)行任務(wù)按照優(yōu)先級(jí)和隊(duì)列策略分發(fā)給空閑執(zhí)行節(jié)點(diǎn)。執(zhí)行節(jié)點(diǎn)拿到任務(wù)之后在自己的環(huán)境里把用例跑起來(lái)結(jié)束后回傳狀態(tài)和產(chǎn)物。這里的“簡(jiǎn)單”是事后總結(jié)的過(guò)程中反復(fù)改過(guò)三輪。最初是直接同步調(diào)用一個(gè)任務(wù)跑起來(lái)接口卡住測(cè)試人員以為平臺(tái)掛了其實(shí)只是用例還沒(méi)跑完。后來(lái)改成異步任務(wù)又面臨新的問(wèn)題怎么控制同時(shí)跑多少個(gè)任務(wù)、怎么避免同一個(gè)用例被定時(shí)和手動(dòng)觸發(fā)重復(fù)執(zhí)行。最終我們引入了隊(duì)列加并發(fā)信號(hào)量執(zhí)行節(jié)點(diǎn)回調(diào)上報(bào)調(diào)度器通過(guò)內(nèi)存緩存中間件維護(hù)鎖狀態(tài)這才穩(wěn)定下來(lái)。失敗重試策略也要提前定清楚。我們是區(qū)分“斷言失敗”和“環(huán)境失敗”的斷言失敗代表業(yè)務(wù)邏輯不符重試沒(méi)有意義環(huán)境失敗比如依賴下載超時(shí)、執(zhí)行節(jié)點(diǎn)失聯(lián)才會(huì)觸發(fā)自動(dòng)重試最多兩次。如果不區(qū)分就盲目重試遇到真正的缺陷時(shí)報(bào)告反而被掩蓋了。3.4 報(bào)告中心與通知觸達(dá)讓結(jié)果自己跑去找人報(bào)告模塊不能只是把執(zhí)行日志原樣貼出來(lái)。我們做了三個(gè)層次單次執(zhí)行的詳細(xì)報(bào)告、多次執(zhí)行的歷史對(duì)比、按項(xiàng)目聚合的質(zhì)量趨勢(shì)。單次詳細(xì)報(bào)告包含用例狀態(tài)、失敗步驟、日志摘錄、耗時(shí)分布?xì)v史對(duì)比展示同一個(gè)用例最近十次的通過(guò)率和平均耗時(shí)質(zhì)量趨勢(shì)按周聚合整體通過(guò)率方便團(tuán)隊(duì)周會(huì)上直接引用。通知的優(yōu)先級(jí)也做了設(shè)計(jì)。全量通過(guò)的任務(wù)只在報(bào)告頁(yè)更新消息通道發(fā)一條簡(jiǎn)潔匯總出現(xiàn)失敗的任務(wù)除了匯總消息還會(huì)把失敗用例編號(hào)和日志入口帶上相關(guān)負(fù)責(zé)人在手機(jī)上就能點(diǎn)進(jìn)去看。這里有個(gè)小經(jīng)驗(yàn)不要所有結(jié)果都往群里扔不然兩天之后所有人都會(huì)把消息靜音真正重要的失敗反而被淹沒(méi)。4. 最容易被忽視的技術(shù)細(xì)節(jié)日志、環(huán)境隔離與調(diào)度一致性平臺(tái)表面上的功能誰(shuí)都能做差距往往藏在細(xì)節(jié)里。下面幾個(gè)問(wèn)題是我們花了最多時(shí)間處理的每一個(gè)單拿出來(lái)都值得展開(kāi)說(shuō)。4.1 用例日志實(shí)時(shí)回傳print只是開(kāi)始最早做執(zhí)行記錄時(shí)日志是等用例跑完一次性上傳的。用例短還好跑十分鐘以上的用例測(cè)試人員盯著空白屏幕完全不知道進(jìn)行到哪一步心里特別沒(méi)底。后來(lái)我們改成實(shí)時(shí)回傳執(zhí)行節(jié)點(diǎn)把用例進(jìn)程的標(biāo)準(zhǔn)輸出流實(shí)時(shí)讀取出來(lái)按大小分塊通過(guò)長(zhǎng)連接通道推到前端。這里有個(gè)容易踩的坑測(cè)試腳本里cout輸出經(jīng)常因?yàn)榫彌_導(dǎo)致不能及時(shí)落盤(pán)如果只是簡(jiǎn)單重定向到文件日志會(huì)攢一大波才刷出來(lái)。我們要求用例執(zhí)行器啟動(dòng)時(shí)強(qiáng)制禁用輸出緩沖同時(shí)按行讀取保證前端能在幾乎實(shí)時(shí)的延遲內(nèi)看到日志。再往后追加了日志級(jí)別過(guò)濾前端可以只顯示警告和報(bào)錯(cuò)否則成功用例刷屏?xí)r真正有用的信息會(huì)被沖走。4.2 執(zhí)行環(huán)境隔離每次跑都像第一次跑自動(dòng)化測(cè)試最怕“上次能跑這次不能跑”環(huán)境臟是頭號(hào)嫌疑犯。執(zhí)行節(jié)點(diǎn)如果復(fù)用全局環(huán)境不同用例之間會(huì)產(chǎn)生依賴污染A用例升級(jí)了某個(gè)依賴庫(kù)B用例再跑就掛這種問(wèn)題排查起來(lái)極其崩潰。我們的做法是每條用例在獨(dú)立的臨時(shí)目錄里執(zhí)行依賴按用例聲明固定版本執(zhí)行過(guò)程中寫(xiě)入的環(huán)境變量只對(duì)當(dāng)前進(jìn)程生效進(jìn)程退出即銷毀。端口資源也做了分段分配避免并發(fā)用例搶同一個(gè)端口。這套隔離方案執(zhí)行完之后環(huán)境相關(guān)的隨機(jī)失敗率從之前的接近百分之十降到了百分之一以下。隔離帶來(lái)額外開(kāi)銷比如每次執(zhí)行都要重新準(zhǔn)備依賴但對(duì)比偶發(fā)問(wèn)題浪費(fèi)的排查時(shí)間這很劃算。4.3 調(diào)度與分布式鎖不讓任務(wù)重復(fù)執(zhí)行定時(shí)任務(wù)最怕什么重復(fù)執(zhí)行。我們遇到過(guò)定時(shí)觸發(fā)和手動(dòng)觸發(fā)同時(shí)命中同一組用例跑了兩遍報(bào)告對(duì)不上。根因是調(diào)度器沒(méi)有做任務(wù)級(jí)別的唯一性約束。后來(lái)我們?cè)谌蝿?wù)生成環(huán)節(jié)加了分布式鎖用唯一鍵表示“同一用例同一觸發(fā)源在時(shí)間窗口內(nèi)只能有一個(gè)待執(zhí)行任務(wù)”鎖的持有時(shí)間設(shè)置為三十分鐘超時(shí)自動(dòng)釋放。同時(shí)在任務(wù)表里建了唯一索引作為兜底雙保險(xiǎn)之后重復(fù)執(zhí)行基本絕跡。需要注意鎖的粒度要按用例維度而不是全局維度否則一個(gè)用例跑完了其他用例全被堵住并發(fā)能力直接廢掉。這個(gè)設(shè)計(jì)可以用下面這段偽代碼表達(dá)lock_key ftask_lock_{case_id}_{source} # 同一用例同一觸發(fā)源120秒內(nèi)只能生成一次任務(wù) if acquire_lock(lock_key, timeout120): create_task(case_id, source) else: log_skip(case_id, source)4.4 資源并發(fā)控制別讓一堆任務(wù)拖垮執(zhí)行節(jié)點(diǎn)執(zhí)行節(jié)點(diǎn)資源是有限的并發(fā)任務(wù)太多互相搶CPU每個(gè)用例都跑得慢最后全都超時(shí)失敗。我們給每個(gè)執(zhí)行節(jié)點(diǎn)設(shè)置了最大并發(fā)數(shù)調(diào)度器分配任務(wù)之前先檢查節(jié)點(diǎn)剩余容量超出容量的任務(wù)留在隊(duì)列里等待。這里有一點(diǎn)要特別提醒并發(fā)數(shù)不能只看執(zhí)行節(jié)點(diǎn)的物理核數(shù)還要看用例本身的特點(diǎn)。接口類用例輕可以多跑幾個(gè)UI自動(dòng)化用例重并發(fā)稍微一高瀏覽器窗口一多內(nèi)存就吃緊了。我們是按歷史執(zhí)行數(shù)據(jù)給每類用例打了資源權(quán)重輕量級(jí)用十、重量級(jí)用一權(quán)重之和不超過(guò)節(jié)點(diǎn)容量才允許下發(fā)。有了這套機(jī)制平臺(tái)再也不用擔(dān)心某個(gè)重任務(wù)把節(jié)點(diǎn)拖死。5. 從零到一落地順序先跑通再治理平臺(tái)的技術(shù)方案只是前半程真正難的是把它推進(jìn)團(tuán)隊(duì)日常使用。我們的落地節(jié)奏分了六個(gè)階段每一步都有明確目標(biāo)。5.1 階段一盤(pán)點(diǎn)存量資產(chǎn)知道手里有什么動(dòng)手之前先摸底。我們把團(tuán)隊(duì)現(xiàn)有的自動(dòng)化腳本全部盤(pán)點(diǎn)一遍記錄每個(gè)腳本對(duì)應(yīng)的業(yè)務(wù)模塊、執(zhí)行方式、是否有依賴外部環(huán)境、上次運(yùn)行時(shí)間。盤(pán)點(diǎn)結(jié)果很有意思大約四成的腳本已經(jīng)沒(méi)人維護(hù)跑不通真正活躍在用的只有一半左右。這個(gè)盤(pán)點(diǎn)幫我們避免了一個(gè)大坑——如果盲目把所有腳本都遷移到新平臺(tái)等于把一堆僵尸腳本也搬了進(jìn)來(lái)平臺(tái)一上線就是一片紅大家第一印象就毀了。5.2 階段二最小閉環(huán)先行不做大而全平臺(tái)第一版只實(shí)現(xiàn)了三個(gè)功能上傳用例、手動(dòng)觸發(fā)執(zhí)行、查看報(bào)告。沒(méi)有定時(shí)調(diào)度沒(méi)有消息通知也沒(méi)有復(fù)雜的權(quán)限體系。因?yàn)槲覀兊哪繕?biāo)是驗(yàn)證一條主干鏈路能不能跑通而不是一開(kāi)始就做一個(gè)完美的平臺(tái)。這三件事看著少但把用例入庫(kù)、任務(wù)生成、執(zhí)行分發(fā)、結(jié)果回傳、報(bào)告展示都串了起來(lái)后續(xù)加功能只是在這個(gè)骨架上長(zhǎng)肉。第一版花了很短時(shí)間就交付了團(tuán)隊(duì)內(nèi)部試用時(shí)大家第一次體驗(yàn)到“腳本傳上去就能在平臺(tái)點(diǎn)執(zhí)行然后直接在網(wǎng)頁(yè)上看日志”的感覺(jué)反饋比想象中積極。這個(gè)正反饋非常關(guān)鍵它讓團(tuán)隊(duì)成員愿意繼續(xù)提供需求而不是把平臺(tái)當(dāng)成一個(gè)行政指令下的負(fù)擔(dān)。5.3 階段三試點(diǎn)項(xiàng)目?jī)?yōu)先讓數(shù)據(jù)說(shuō)話全量推廣之前我們選了兩個(gè)核心業(yè)務(wù)線作為試點(diǎn)一條是接口測(cè)試用例比較成熟的另一條是UI自動(dòng)化用例相對(duì)薄弱的。試點(diǎn)有一個(gè)明確目標(biāo)連續(xù)跑兩周接口用例通過(guò)率穩(wěn)定在某個(gè)預(yù)期值以上UI用例能穩(wěn)定產(chǎn)出失敗證據(jù)。兩周試點(diǎn)的結(jié)果給了我們很有價(jià)值的決策依據(jù)接口用例整體通過(guò)率良好但有百分之十幾的失敗集中在依賴測(cè)試數(shù)據(jù)被清理的場(chǎng)景UI用例失敗里超過(guò)一半是等待超時(shí)跟被測(cè)頁(yè)面的異步加載有關(guān)系。如果沒(méi)有平臺(tái)把結(jié)果集中統(tǒng)計(jì)這些規(guī)律靠人工看散落日志根本發(fā)現(xiàn)不了。試點(diǎn)結(jié)束后我們用這些數(shù)據(jù)說(shuō)服了還在觀望的團(tuán)隊(duì)推廣阻力小了很多。5.4 階段四接進(jìn)發(fā)布流程自動(dòng)化測(cè)試從輔助變成門(mén)禁平臺(tái)跑順之后我們把執(zhí)行能力開(kāi)放成接口接入到團(tuán)隊(duì)的流水線里。流水線在部署完成后自動(dòng)觸發(fā)平臺(tái)執(zhí)行冒煙測(cè)試集測(cè)試通過(guò)才允許繼續(xù)發(fā)布。這一步是自動(dòng)化測(cè)試平臺(tái)價(jià)值最大化的關(guān)鍵轉(zhuǎn)折它讓測(cè)試不再是一個(gè)事后核查環(huán)節(jié)而是發(fā)布流程里的必經(jīng)關(guān)卡。剛開(kāi)始接流水線時(shí)一定要輕。我們只把冒煙級(jí)別用例設(shè)為門(mén)禁回歸級(jí)別用例仍然異步執(zhí)行避免發(fā)布被大量無(wú)關(guān)失敗阻塞。等到平臺(tái)穩(wěn)定性充分驗(yàn)證之后再逐步收緊。門(mén)禁的意義不只是攔問(wèn)題更是給自動(dòng)化測(cè)試用例提出明確的質(zhì)量要求誤報(bào)率太高會(huì)被取消門(mén)禁資格測(cè)試人員為了保住門(mén)禁會(huì)主動(dòng)維護(hù)用例可靠性這是平臺(tái)運(yùn)營(yíng)中最有價(jià)值的正向循環(huán)。5.5 階段五用例治理與運(yùn)營(yíng)推廣平臺(tái)不是搭完就結(jié)束后面持續(xù)運(yùn)營(yíng)才是重頭。我們每月做一次用例健康度檢查統(tǒng)計(jì)每個(gè)用例近三十天的執(zhí)行次數(shù)、通過(guò)率、平均耗時(shí)、最近更新時(shí)間列出一個(gè)“僵尸用例清單”發(fā)給各業(yè)務(wù)負(fù)責(zé)人。要么修要么刪不允許長(zhǎng)期不行不維護(hù)的用例占著平臺(tái)資源。另一個(gè)運(yùn)營(yíng)抓手是質(zhì)量周報(bào)。平臺(tái)自動(dòng)生成一份按業(yè)務(wù)線分組的測(cè)試質(zhì)量周報(bào)包含用例總數(shù)、執(zhí)行次數(shù)、通過(guò)率、失敗Top用例、耗時(shí)變化直接發(fā)到團(tuán)隊(duì)例會(huì)材料里。質(zhì)量數(shù)據(jù)一旦變成固定議程團(tuán)隊(duì)對(duì)自動(dòng)化測(cè)試的重視程度就完全不一樣了新增用例的行為也會(huì)越來(lái)越主動(dòng)。6. 上線半年踩過(guò)的坑與排查思路平臺(tái)運(yùn)行半年我們記錄了一串實(shí)際踩過(guò)的問(wèn)題。每個(gè)問(wèn)題單獨(dú)看似乎都不大但遇到時(shí)確實(shí)能讓整個(gè)平臺(tái)卡住下面把排查思路一并寫(xiě)出來(lái)。6.1 任務(wù)消失與重復(fù)執(zhí)行狀態(tài)機(jī)與恢復(fù)機(jī)制現(xiàn)象是有時(shí)候平臺(tái)頁(yè)面上顯示“待執(zhí)行”的任務(wù)過(guò)了一段時(shí)間變成“執(zhí)行中”但執(zhí)行節(jié)點(diǎn)上根本沒(méi)有任何進(jìn)程在跑。排查時(shí)我們先是懷疑調(diào)度器宕了檢查后發(fā)現(xiàn)是調(diào)度器重啟的時(shí)候把內(nèi)存里的運(yùn)行中狀態(tài)丟掉了任務(wù)既沒(méi)有重新分發(fā)也沒(méi)有被標(biāo)記為失敗就一直懸在中間狀態(tài)。修復(fù)方案分兩層。第一層是把任務(wù)狀態(tài)流轉(zhuǎn)全部顯式化任何狀態(tài)的變更都必須寫(xiě)數(shù)據(jù)庫(kù)調(diào)度器重啟后掃描所有“執(zhí)行中”的任務(wù)如果發(fā)現(xiàn)對(duì)應(yīng)節(jié)點(diǎn)心跳超時(shí)就將任務(wù)重置為“待執(zhí)行”并重新分發(fā)。第二層是執(zhí)行節(jié)點(diǎn)增加心跳上報(bào)節(jié)點(diǎn)失聯(lián)超過(guò)閾值會(huì)告警避免任務(wù)卡死在失聯(lián)狀態(tài)里。加上這兩層之后任務(wù)狀態(tài)再?zèng)]出現(xiàn)過(guò)懸停。6.2 報(bào)告頁(yè)面越跑越慢分頁(yè)、異步與歸檔平臺(tái)上線兩個(gè)月后報(bào)告接口響應(yīng)從一兩秒退化到十幾秒。剛開(kāi)始以為是數(shù)據(jù)庫(kù)問(wèn)題查了之后發(fā)現(xiàn)執(zhí)行記錄表確實(shí)漲得很快但加了索引以后效果不明顯。真正原因是前端一次性把整個(gè)任務(wù)的所有日志和截圖都渲染出來(lái)了用例一多響應(yīng)體就是好幾兆。解決方案是報(bào)告接口強(qiáng)制分頁(yè)加按需加載列表頁(yè)只返回概要字段詳細(xì)日志按行倒序分批加載截圖文件走獨(dú)立的對(duì)象存儲(chǔ)鏈接不在接口響應(yīng)里內(nèi)嵌。另外把超過(guò)三十天的報(bào)告沉淀到歸檔存儲(chǔ)查詢主庫(kù)只保留活躍數(shù)據(jù)。調(diào)整之后報(bào)告頁(yè)面不管數(shù)據(jù)量多大首屏加載都控制在一兩秒內(nèi)。6.3 偶發(fā)失敗最難查環(huán)境污染與共享數(shù)據(jù)有一類失敗最讓人崩潰同一個(gè)用例這次跑通過(guò)下次跑失敗看日志又完全正常。我們追蹤過(guò)好幾個(gè)案例根源幾乎都跟共享資源有關(guān)。典型的是測(cè)試用例依賴了公共測(cè)試賬號(hào)另一個(gè)用例執(zhí)行時(shí)把賬號(hào)狀態(tài)改了導(dǎo)致這邊登錄失敗還有一個(gè)用例讀到共享測(cè)試數(shù)據(jù)庫(kù)里的臟數(shù)據(jù)斷言的時(shí)候突然對(duì)不上。這類問(wèn)題的核心解法就是徹底隔離公共賬號(hào)改造成每個(gè)任務(wù)單獨(dú)準(zhǔn)備測(cè)試數(shù)據(jù)對(duì)象數(shù)據(jù)庫(kù)場(chǎng)景要求用例執(zhí)行前自己構(gòu)造數(shù)據(jù)、結(jié)束后自行清理。平臺(tái)層面增加了“失敗前環(huán)境快照”在用例執(zhí)行期間采樣系統(tǒng)狀態(tài)和關(guān)鍵文件變化偶發(fā)失敗時(shí)能提供更多線索。環(huán)境治理是自動(dòng)化測(cè)試長(zhǎng)期穩(wěn)定繞不開(kāi)的功課越早做越劃算。6.4 超時(shí)、等待與清理細(xì)節(jié)清單最后整理一份我們反復(fù)對(duì)團(tuán)隊(duì)強(qiáng)調(diào)的檢查清單每條背后都有一次真實(shí)的故障案例等待元素或接口響應(yīng)優(yōu)先使用輪詢等待而不是固定休眠固定休眠在環(huán)境慢的時(shí)候非常脆弱。用例超時(shí)設(shè)置不能只有一個(gè)總超時(shí)最好拆成“建立連接超時(shí)”“整體執(zhí)行超時(shí)”“用例間等待超時(shí)”分別設(shè)值。執(zhí)行結(jié)束后清理臨時(shí)文件和數(shù)據(jù)庫(kù)寫(xiě)入不給后續(xù)用例留坑。用例命名必須包含業(yè)務(wù)模塊和用例編號(hào)否則報(bào)告里出現(xiàn)一堆同名“test_1”查歸屬直接崩潰。執(zhí)行節(jié)點(diǎn)的時(shí)間和時(shí)區(qū)要統(tǒng)一分布式環(huán)境里時(shí)間不一致調(diào)度記錄和日志排查會(huì)非常痛苦。這些細(xì)節(jié)看起來(lái)瑣碎但平臺(tái)穩(wěn)定性的提升往往不是靠一個(gè)驚天設(shè)計(jì)而是靠把這些瑣碎問(wèn)題一個(gè)一個(gè)磨平?;仡^總結(jié)一下這段經(jīng)歷我最大的體會(huì)是自動(dòng)化測(cè)試平臺(tái)最難的部分始終不是技術(shù)而是讓團(tuán)隊(duì)真正把它用起來(lái)。一上來(lái)就能被廣泛接受的平臺(tái)幾乎不存在先跑通最小閉環(huán)用數(shù)據(jù)打動(dòng)觀望者再把門(mén)禁和運(yùn)營(yíng)機(jī)制一步步補(bǔ)上路子就順了。如果你想搭一套這樣的平臺(tái)別急著把界面做得花哨先讓“上傳用例、觸發(fā)執(zhí)行、看到報(bào)告”這三個(gè)動(dòng)作變得無(wú)比順暢后面的事情都會(huì)自然長(zhǎng)出來(lái)。