作者:彭海波
前言
Android App的性能測(cè)試是移動(dòng)測(cè)試過(guò)程中必不可少的一個(gè)環(huán)節(jié)并齐。在我們項(xiàng)目組內(nèi),性能測(cè)試的過(guò)程是這樣的例书,先設(shè)置測(cè)試場(chǎng)景混驰,然后一邊手工執(zhí)行場(chǎng)景攀隔,一邊通過(guò)工具獲取性能數(shù)據(jù),為了減少誤差账胧,一個(gè)場(chǎng)景一般重復(fù)執(zhí)行3-5次竞慢,測(cè)試完后將各種性能數(shù)據(jù)整理成報(bào)告。這個(gè)過(guò)程如果是一個(gè)經(jīng)驗(yàn)豐富的測(cè)試工程師去做治泥,可能要花半天到一天時(shí)間,而如果是一個(gè)新人去做遮精,甚至可能要花兩天時(shí)間居夹。而且有時(shí)候還會(huì)遇到败潦,剛測(cè)完,開(kāi)發(fā)說(shuō)優(yōu)化了性能准脂,又需要重新測(cè)試劫扒。這還只是一個(gè)Android端的時(shí)間,如果再加上iOS端狸膏,時(shí)間加倍沟饥。對(duì)于這樣一個(gè)現(xiàn)狀,我們急需探索出一種自動(dòng)化的方式提高性能測(cè)試的效率湾戳。本文接下來(lái)將要介紹云測(cè)試平臺(tái)在自動(dòng)化性能測(cè)試方面的探索和成果贤旷。
需求分析
作為一個(gè)負(fù)責(zé)任的測(cè)試開(kāi)發(fā)工程師,我們首先要搞清楚需求砾脑,即產(chǎn)品線的性能測(cè)試到底要怎么測(cè)幼驶,要測(cè)哪些數(shù)據(jù)。于是韧衣,我跟著參與了產(chǎn)品線的性能測(cè)試方案及標(biāo)準(zhǔn)的討論盅藻。了解到的性能測(cè)試需要采集的數(shù)據(jù)有:1.響應(yīng)時(shí)間;2.耗電量畅铭;3.CPU氏淑,內(nèi)存消耗,網(wǎng)絡(luò)流量硕噩;4.渲染幀率夸政。而這幾組數(shù)據(jù)需要覆蓋的測(cè)試場(chǎng)景大概有16種,如下表所示榴徐。
||響應(yīng)時(shí)間|CPU|內(nèi)存|耗電量|網(wǎng)絡(luò)流量|流暢度|
|-|-|-|-|-|-|
|首次APP啟動(dòng)到首頁(yè)加載完成|yes|yes|yes|no|yes|no|
|生活-繳費(fèi)|yes|yes|yes|no|yes|yes|
|生活-查違章|yes|yes|yes|no|yes|yes|
|生活-好醫(yī)生|yes|yes|yes|no|yes|yes|
|生活-添加房|yes|yes|yes|no|yes|yes|
|生活-添加車|yes|yes|yes|no|yes|yes|
|生活-添加隨手記|yes|yes|yes|no|yes|yes|
|側(cè)邊欄頁(yè)面加載|yes|yes|yes|no|yes|yes|
|生活繳費(fèi)-收銀臺(tái)功能|yes|yes|yes|no|yes|yes|
|查違章繳費(fèi)-收銀臺(tái)功能|yes|yes|yes|no|yes|yes|
|首頁(yè)滑動(dòng)|no|no|no|no|no|yes|
|Tab頁(yè)切換|no|no|no|no|no|yes|
|生活頁(yè)面滑動(dòng)|no|no|no|no|no|yes|
|理財(cái)頁(yè)滑動(dòng)|no|no|no|no|no|yes|
|App登錄狀態(tài)靜默30分鐘|no|no|no|yes|no|no|
|App非登錄狀態(tài)靜默30分鐘|no|no|no|yes|no|no|
方案調(diào)研
UI自動(dòng)化與性能測(cè)試相結(jié)合
在Android端守问,獲取性能數(shù)據(jù)的方式有很多種,可以通過(guò)開(kāi)源工具采集坑资,可以通過(guò)adb命令獲取耗帕,也可以調(diào)用系統(tǒng)API獲取。各種方法采集的數(shù)據(jù)基本差別不大袱贮,重點(diǎn)是場(chǎng)景操作和數(shù)據(jù)整理比較麻煩仿便。既然要提高測(cè)試效率,那最好的方式肯定是通過(guò)自動(dòng)化來(lái)解決攒巍。自動(dòng)化的框架有很多種嗽仪,這里就不一一做討論了,我們采用的是Appium框架柒莉,但是做了二次封裝和改進(jìn)闻坚,易用性更好。關(guān)于云測(cè)試平臺(tái)UI自動(dòng)化功能的使用方法介紹兢孝,大家可以參考云測(cè)試平臺(tái)幫助文檔一文窿凤。
測(cè)試方案
對(duì)于CPU仅偎,內(nèi)存,F(xiàn)PS雳殊,流量這幾組數(shù)據(jù)橘沥,云測(cè)平臺(tái)早就可以提供測(cè)試了。通過(guò)一個(gè)與UI自動(dòng)化并行的線程來(lái)發(fā)送adb命令獲取應(yīng)用的性能數(shù)據(jù)夯秃。我們需要解決的是如何設(shè)置性能測(cè)試的起點(diǎn)和終點(diǎn)座咆,還有一個(gè)場(chǎng)景多次重復(fù)測(cè)試的問(wèn)題。
那么我們需要想辦法測(cè)試響應(yīng)時(shí)間仓洼,我們采用的方案是設(shè)置一個(gè)待加載頁(yè)面的基準(zhǔn)控件介陶,利用Appium的元素查找功能一直循環(huán)查找,一旦控件找到了衬潦,則認(rèn)為頁(yè)面加載成功斤蔓,這中間的時(shí)間差即為頁(yè)面響應(yīng)時(shí)間。這個(gè)時(shí)間雖然沒(méi)有代碼插樁準(zhǔn)確镀岛,但誤差范圍基本控制在100ms內(nèi)弦牡,對(duì)整體測(cè)試結(jié)果的影響不大。
方案實(shí)施
開(kāi)啟一個(gè)性能測(cè)試的線程
關(guān)于獲取方式前面也介紹了漂羊,話不多說(shuō)驾锰,直接上代碼:
@Override
public void run() {
// TODO Auto-generated method stub
this.running = true;
while (running) {
String time = String.valueOf(System.currentTimeMillis());
time = CalendarDate.GetCurrentTime();
//獲取內(nèi)存數(shù)據(jù)
int [] memArray = AndroidPerformanceTools.getMemoryInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
int totalMem = memArray[0];
int appMem = memArray[1];
//獲取CPU數(shù)據(jù)
int cpuUsage = AndroidPerformanceTools.getCPUInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
//獲取FPS
float fps = AndroidPerformanceTools.getFPSInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
//獲取流量數(shù)據(jù)
long [] trafficArray = AndroidPerformanceTools.getTrafficInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
long totalTrffic = trafficArray[0];
long recTraffic = trafficArray[1];
long sndTraffic = trafficArray[2];
//數(shù)據(jù)初始化
if (this.androidPerformance.getAndroidPerformanceData().getInittotal() == -1
&& totalTrffic > 0) {
this.androidPerformance.getAndroidPerformanceData().setInittotal(totalTrffic);
this.androidPerformance.getAndroidPerformanceData().setInitrec(recTraffic);
this.androidPerformance.getAndroidPerformanceData().setInitsnd(sndTraffic);
}
//匯總數(shù)據(jù)
MemInfo memInfo = new MemInfo(time, totalMem, appMem);
FPSInfo fpsInfo = new FPSInfo(time, fps);
CPUInfo cpuInfo = new CPUInfo(time, cpuUsage);
TrafficInfo trafficInfo = new TrafficInfo(time, totalTrffic, recTraffic, sndTraffic);
this.androidPerformance.getAndroidPerformanceData().getCpuinfolist().add(cpuInfo);
this.androidPerformance.getAndroidPerformanceData().getMeminfolist().add(memInfo);
this.androidPerformance.getAndroidPerformanceData().getTraffinfolist().add(trafficInfo);
this.androidPerformance.getAndroidPerformanceData().getFpsinfolist().add(fpsInfo);
//設(shè)置采集間隔時(shí)間
try {
Thread.sleep(this.sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
設(shè)置開(kāi)始和結(jié)束的點(diǎn)
由于UI自動(dòng)化的執(zhí)行每次都是從啟動(dòng)App開(kāi)始,但性能數(shù)據(jù)的收集卻是針對(duì)具體某個(gè)場(chǎng)景的走越,比如進(jìn)入生活繳費(fèi)頁(yè)面椭豫。因此為了方便設(shè)置性能測(cè)試的開(kāi)始和結(jié)束時(shí)間,我們?cè)赨I自動(dòng)化腳本的錄制action中加了兩個(gè)字段:startPerformance和stopPerformance旨指。用戶可以在腳本的任意位置加入這兩個(gè)action赏酥,但主要先后關(guān)系,而且需要配對(duì)使用谆构。當(dāng)我們?cè)趫?zhí)行腳本的時(shí)候裸扶,遇到startPeformance,就啟動(dòng)性能測(cè)試線程搬素,然后遇到stopPerformance呵晨,則停止線程,并保存數(shù)據(jù)熬尺。一個(gè)完整的用于做性能測(cè)試的腳本如下所示:
重復(fù)執(zhí)行
對(duì)于有些場(chǎng)景摸屠,我們?yōu)榱藴p少誤差,通常需要重復(fù)執(zhí)行多次取平均值粱哼。因此季二,我們?cè)谛陆y(cè)試任務(wù)的時(shí)候,增加了設(shè)置重復(fù)次數(shù)的參數(shù)皂吮。測(cè)試任務(wù)會(huì)根據(jù)設(shè)置的參數(shù)戒傻,重復(fù)執(zhí)行相應(yīng)的次數(shù)税手。
響應(yīng)時(shí)間的測(cè)試
頁(yè)面時(shí)間是通過(guò)計(jì)算開(kāi)始加載到某個(gè)關(guān)鍵元素出現(xiàn)的時(shí)間差來(lái)作為衡量的蜂筹,具體實(shí)現(xiàn)方式如下:
@BeforeMethod
public void beforeTest() {
createDriver();
stepStartTime = System.currentTimeMillis();
Log.logger.debug("hello");
}
@Test
public void runStep(){
stepTestTime = System.currentTimeMillis() - stepStartTime;
if(loadingtime == 0)
loadingtime = stepTestTime;
//設(shè)置結(jié)果
if(task.getTasktype().equals("Android 性能測(cè)試")){
JSONObject stepResult = new JSONObject();
stepResult.put("uielement", testStep.getUi_element().find_method_value);
stepResult.put("stepname", testStep.getUi_element().name);
stepResult.put("steptime", stepTestTime);
stepResult.put("stepresult", "success");
stepResultList.add(stepResult);
//taskService.setTaskResultInfo(taskId, stepResultList.toJSONString());
}
}
數(shù)據(jù)匯總與統(tǒng)計(jì)
性能測(cè)試結(jié)束后需纳,我們將數(shù)據(jù)匯總并進(jìn)行簡(jiǎn)單的統(tǒng)計(jì),最終寫入文件艺挪,保存最為測(cè)試報(bào)告的數(shù)據(jù)支持不翩。這里簡(jiǎn)單列舉下CPU的統(tǒng)計(jì)代碼:
ArrayList<CPUInfo> cpuInfos = androidPerformanceData.getCpuinfolist();
JSONArray cpuTimeJsonArray = new JSONArray();
JSONArray cpuDataJsonArray = new JSONArray();
double avgCPU = 0.0;
long total = 0;
for (CPUInfo cpuInfo : cpuInfos) {
total += cpuInfo.cpuUsage;
cpuTimeJsonArray.add(cpuInfo.time);
cpuDataJsonArray.add(cpuInfo.cpuUsage);
}
avgCPU = total/cpuInfos.size();
成果展示
整體性能報(bào)告
詳細(xì)性能報(bào)告
詳細(xì)性能報(bào)告主要顯示每次執(zhí)行的響應(yīng)時(shí)間結(jié)果,CPU麻裳,內(nèi)存口蝠,流量消耗以及FPS的分布曲線。這里簡(jiǎn)單列舉其中兩項(xiàng)數(shù)據(jù)的截圖如下津坑。
總結(jié)
至此妙蔗,我們通過(guò)UI自動(dòng)化與性能測(cè)試結(jié)合的方式解決了Android App自動(dòng)化性能測(cè)試的問(wèn)題。而且從腳本的錄制疆瑰,到測(cè)試執(zhí)行眉反,報(bào)告的生成都已經(jīng)平臺(tái)化。測(cè)試人員只需要錄制非常簡(jiǎn)單的腳本穆役,即可一鍵得到相應(yīng)的性能測(cè)試報(bào)告寸五。當(dāng)然,這種方式也有其缺點(diǎn)耿币,比如響應(yīng)時(shí)間的測(cè)試有些小誤差梳杏,錄制腳本有一定的學(xué)習(xí)成本。這些都是我們未來(lái)要優(yōu)化的方向淹接,歡迎大家提出改進(jìn)建議十性。