更新時(shí)間:2018-10-26 來(lái)源:黑馬程序員技術(shù)社區(qū) 瀏覽量:
模塊級(jí)別自動(dòng)化測(cè)試的經(jīng)驗(yàn)與教訓(xùn)概述
搞了幾個(gè)月的自動(dòng)化測(cè)試,結(jié)果不甚理想,這里做一個(gè)簡(jiǎn)單的總結(jié)。
為什么要做自動(dòng)化測(cè)試呢?因?yàn)槭止y(cè)試效率低,找 case、執(zhí)行 case 太費(fèi)時(shí)間。
為什么自動(dòng)化測(cè)試前面要加“模塊級(jí)別”呢?因?yàn)橐粋€(gè)系統(tǒng)依賴很多外部系統(tǒng),如果不能有效屏蔽外部環(huán)境的差異,自動(dòng)化測(cè)試會(huì)經(jīng)常因?yàn)榄h(huán)境問(wèn)題而失敗。
原理
這里講的自動(dòng)化測(cè)試原理是,在線上環(huán)境錄制測(cè)試 case,在線下測(cè)試環(huán)境利用錄制的 case 進(jìn)行回放測(cè)試。
如何錄制 case?某個(gè)系統(tǒng)對(duì)一次請(qǐng)求的一次處理可以認(rèn)為是一個(gè) case,模塊級(jí)別的自動(dòng)化測(cè)試要求錄制該系統(tǒng)在處理請(qǐng)求時(shí)的所有對(duì)外交互,包括但不限于:
該模塊接收到的請(qǐng)求和對(duì)外響應(yīng)信息。該模塊在處理請(qǐng)求過(guò)程中,產(chǎn)生的所有下游調(diào)用的請(qǐng)求和響應(yīng)。常見(jiàn)的有:HTTP 請(qǐng)求、DB 請(qǐng)求、Redis 查詢、MQ 輸出、熱更新配置等等。
以下圖為例,有 A、B、C、D 四個(gè)系統(tǒng)模塊,假設(shè)要對(duì)模塊 A 做自動(dòng)化測(cè)試,需要錄制測(cè)試 case。該模塊有一個(gè)接口,接收來(lái)自模塊 D 的請(qǐng)求,在處理過(guò)程中會(huì)產(chǎn)生對(duì)模塊 B、C 的下游調(diào)用。那么在錄制 case 時(shí),不僅要錄制該接口的輸入輸出,也有錄制兩次下游調(diào)用的輸入和輸出。
在請(qǐng)求處理過(guò)程中,往往會(huì)出現(xiàn)各種線程的切換、異步調(diào)用,如何將一個(gè)請(qǐng)求的所有對(duì)外交互串起來(lái)呢?很多公司都有分布式鏈路跟蹤系統(tǒng),會(huì)有一個(gè) traceID 能夠把不同系統(tǒng)的請(qǐng)求串聯(lián)起來(lái),同理也可以利用該 traceID 把單個(gè)系統(tǒng)內(nèi)部的請(qǐng)求處理過(guò)程串聯(lián)起來(lái),擁有同一個(gè) traceID 的輸入輸出可以認(rèn)為是屬于同一個(gè) case 的。
case 的錄制思路比較簡(jiǎn)單,收集數(shù)據(jù)后存儲(chǔ)到 DB 中即可。case 的回放測(cè)試分為兩步:
運(yùn)行測(cè)試 case。DIFF 測(cè)試結(jié)果,需要進(jìn)行 DIFF 的數(shù)據(jù)包括測(cè)試接口的響應(yīng)參數(shù)、下游調(diào)用的請(qǐng)求參數(shù),也就是被測(cè)模塊對(duì)外輸出的數(shù)據(jù)。
經(jīng)歷的坑本地緩存的 dump 問(wèn)題
前面說(shuō)了,錄制 case 時(shí),需要錄制所有的下游調(diào)用的請(qǐng)求和響應(yīng)。對(duì)于需要定期更新的本地緩存,也需要 dump 其數(shù)據(jù),如何 dump 呢?dump 查緩存的方法即可,以下面代碼為例,dump 方法 getXXXConfig 的輸入輸出即可。本質(zhì)上是將 getXXXConfig 方法的調(diào)用當(dāng)成了外部請(qǐng)求進(jìn)行錄制。
@Component
public class XXXCache {
private LoadingCache<String, List<XXX>> xxxCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).build(
new CacheLoader<String, List<XXX>>() {
public List<XXX> load(String key) {
// 省略
}
});
public List<XXX> getXXXConfig(String param) {
try {
return xxxCache.get(param);
}catch (Exception e) {
logger.error("get xxx error, param:{}", e, param);
}
return ListUtils.EMPTY_LIST;
}
}
性能問(wèn)題
收集數(shù)據(jù)必定會(huì)耗費(fèi)一些性能,為了減輕對(duì)系統(tǒng)的壓力,錄制時(shí)增加了采樣率的概念,只錄制少量數(shù)據(jù)。
多數(shù)情況下,降低采樣率可以解決性能問(wèn)題,但也有例外。在對(duì)接某個(gè)系統(tǒng)時(shí)發(fā)現(xiàn)其有性能問(wèn)題,CPU 從 20% 漲到了 30%,發(fā)現(xiàn)一次請(qǐng)求里要錄制 5000 條下游調(diào)用,我們將采樣率降低到 10 分鐘錄制一個(gè) case 依然無(wú)法解決問(wèn)題。那么到底是哪里耗費(fèi)性能呢?研究后發(fā)現(xiàn),是在判斷是否收集數(shù)據(jù)的方法里比較耗費(fèi)性能,該方法在每次有下游調(diào)用時(shí)都需要用來(lái)判斷是否需要收集數(shù)據(jù)。該方法代碼如下所示,其中有兩點(diǎn)影響性能:
在低并發(fā)的情況下,ConcurrentHashMap 競(jìng)爭(zhēng)較少影響不大,然而在高并發(fā)情況下競(jìng)爭(zhēng)較多,性能下降。
inRecordEnv 是一個(gè) volatile 變量,會(huì)引起 CPU 緩存失效,少量影響不大,但是一次請(qǐng)求里 5000 次 CPU 緩存失效影響就大了。
public volatile boolean inRecordEnv;
public ConcurrentHashMap<String, TraceContext> recordingTraceIdMap;
public boolean needRecord(String traceId) {
if (! inRecordEnv) {
return false;
}
return recordingTraceIdMap.contains(traceId);
}
解決該問(wèn)題的辦法有兩個(gè):
將是否需要錄制放到線程本地變量中,跟 traceID 一起傳遞,這需要改到分布式鏈路跟蹤組件。
case 錄制一般是選擇集群中的某一臺(tái)服務(wù)器進(jìn)行錄制的,那么修改負(fù)載均衡機(jī)制,降低錄制 case 集群的權(quán)重,即可減輕其壓力。同時(shí),需要稍作處理,讓其他機(jī)器需要不走錄制判斷邏輯,畢竟單單是錄制判斷邏輯就足以影響性能了。
靜態(tài)方法、隨機(jī)數(shù)問(wèn)題
有一些緩存的查詢用的是靜態(tài)方法,靜態(tài)方法因?yàn)椴槐?Spring 容器托管,是無(wú)法使用 Spring AOP 處理的。對(duì)于此類方法,如果要 dump 數(shù)據(jù),就必須使用 java instrument 字節(jié)碼修改技術(shù)。
與之類似的還有隨機(jī)數(shù)生成器、UUID 生成,這些隨機(jī)數(shù)往往是某個(gè) Redis 值的 key,也需要去 mock 住才能保證測(cè)試成功。
帶異步任務(wù)的請(qǐng)求結(jié)束時(shí)間判斷問(wèn)題
在業(yè)務(wù)處理中,為了提升處理速度,經(jīng)常會(huì)啟動(dòng)多個(gè)線程同時(shí)處理,處理完畢后再合并請(qǐng)求結(jié)果返回。
在一些特殊情況下,業(yè)務(wù)處理過(guò)程中,會(huì)啟動(dòng)異步任務(wù)去計(jì)算,主線程不等待異步任務(wù)結(jié)束便返回結(jié)果。而該異步任務(wù)的結(jié)果會(huì)存在分布式緩存 Redis 中,供下一階段業(yè)務(wù)處理使用。問(wèn)題產(chǎn)生在原因是,我們需要錄制主線程和異步任務(wù)的數(shù)據(jù),但是難以判斷請(qǐng)求的處理何時(shí)真正結(jié)束。一般都是在接口返回?cái)?shù)據(jù)給調(diào)用方時(shí),就認(rèn)為請(qǐng)求處理結(jié)束了,此 case 的數(shù)據(jù)錄制完成。而這里需要等待異步任務(wù)結(jié)束,才算真正錄制完成。
解決辦法是:新增任務(wù)標(biāo)記 API,用于標(biāo)記異步任務(wù)的啟動(dòng)和結(jié)束,在業(yè)務(wù)代碼中進(jìn)行標(biāo)記。
代碼規(guī)范問(wèn)題
在業(yè)務(wù)系統(tǒng)中引入模塊級(jí)自動(dòng)化測(cè)試時(shí),發(fā)現(xiàn)一些通用的問(wèn)題:
一些 Bean 缺少默認(rèn)構(gòu)造函數(shù):因?yàn)樯婕暗?Mock 數(shù)據(jù)的反序列化,需要這些 Bean 有默認(rèn)構(gòu)造函數(shù)。traceID 傳遞過(guò)程中丟失:因?yàn)殇浿普?qǐng)求依賴 traceID 來(lái)判斷是否錄制,所以在線程切換、異步回調(diào)時(shí),必須保證 traceID 的正確傳遞。TimeStamp 問(wèn)題:一些請(qǐng)求參數(shù)里會(huì)有當(dāng)前時(shí)間的信息,時(shí)間信息在回放時(shí),會(huì)發(fā)生變化,導(dǎo)致測(cè)試 DIFF 失敗。解決辦法有兩個(gè),一是測(cè)試時(shí)修改系統(tǒng)時(shí)間,此方法存在精確度的問(wèn)題,二是忽略時(shí)間字段的 DIFF。
總結(jié)
模塊級(jí)自動(dòng)化測(cè)試的思路是:在線上環(huán)境錄制 case,通過(guò)錄制請(qǐng)求處理過(guò)程中的所有對(duì)外交互,實(shí)現(xiàn)對(duì)外部環(huán)境依賴的解耦。在線下測(cè)試環(huán)境回放測(cè)試 case,并且 DIFF 對(duì)于對(duì)外輸出。
在業(yè)務(wù)系統(tǒng)接入過(guò)程中,遇到的問(wèn)題,一些是代碼規(guī)范問(wèn)題,一些是特殊業(yè)務(wù)邏輯造成的問(wèn)題。其中,第二類問(wèn)題嚴(yán)重影響接入效率。
本文版權(quán)歸軟件測(cè)試培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!
作者:軟件測(cè)試培訓(xùn)學(xué)院
首發(fā):http://ko1818.cn/special/testzly/index.html