作者:彭海波
前言
Android App的性能測試是移動(dòng)測試過程中必不可少的一個(gè)環(huán)節(jié)。在我們項(xiàng)目組內(nèi),性能測試的過程是這樣的,先設(shè)置測試場景,然后一邊手工執(zhí)行場景,一邊通過工具獲取性能數(shù)據(jù),為了減少誤差,一個(gè)場景一般重復(fù)執(zhí)行3-5次,測試完后將各種性能數(shù)據(jù)整理成報(bào)告。這個(gè)過程如果是一個(gè)經(jīng)驗(yàn)豐富的測試工程師去做,可能要花半天到一天時(shí)間,而如果是一個(gè)新人去做,甚至可能要花兩天時(shí)間。而且有時(shí)候還會遇到,剛測完,開發(fā)說優(yōu)化了性能,又需要重新測試。這還只是一個(gè)Android端的時(shí)間,如果再加上iOS端,時(shí)間加倍。對于這樣一個(gè)現(xiàn)狀,我們急需探索出一種自動(dòng)化的方式提高性能測試的效率。本文接下來將要介紹云測試平臺在自動(dòng)化性能測試方面的探索和成果。
需求分析
作為一個(gè)負(fù)責(zé)任的測試開發(fā)工程師,我們首先要搞清楚需求,即產(chǎn)品線的性能測試到底要怎么測,要測哪些數(shù)據(jù)。于是,我跟著參與了產(chǎn)品線的性能測試方案及標(biāo)準(zhǔn)的討論。了解到的性能測試需要采集的數(shù)據(jù)有:1.響應(yīng)時(shí)間;2.耗電量;3.CPU,內(nèi)存消耗,網(wǎng)絡(luò)流量;4.渲染幀率。而這幾組數(shù)據(jù)需要覆蓋的測試場景大概有16種,如下表所示。
||響應(yīng)時(shí)間|CPU|內(nèi)存|耗電量|網(wǎng)絡(luò)流量|流暢度|
|-|-|-|-|-|-|
|首次APP啟動(dòng)到首頁加載完成|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è)邊欄頁面加載|yes|yes|yes|no|yes|yes|
|生活繳費(fèi)-收銀臺功能|yes|yes|yes|no|yes|yes|
|查違章繳費(fèi)-收銀臺功能|yes|yes|yes|no|yes|yes|
|首頁滑動(dòng)|no|no|no|no|no|yes|
|Tab頁切換|no|no|no|no|no|yes|
|生活頁面滑動(dòng)|no|no|no|no|no|yes|
|理財(cái)頁滑動(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)化與性能測試相結(jié)合
在Android端,獲取性能數(shù)據(jù)的方式有很多種,可以通過開源工具采集,可以通過adb命令獲取,也可以調(diào)用系統(tǒng)API獲取。各種方法采集的數(shù)據(jù)基本差別不大,重點(diǎn)是場景操作和數(shù)據(jù)整理比較麻煩。既然要提高測試效率,那最好的方式肯定是通過自動(dòng)化來解決。自動(dòng)化的框架有很多種,這里就不一一做討論了,我們采用的是Appium框架,但是做了二次封裝和改進(jìn),易用性更好。關(guān)于云測試平臺UI自動(dòng)化功能的使用方法介紹,大家可以參考云測試平臺幫助文檔一文。
測試方案
對于CPU,內(nèi)存,F(xiàn)PS,流量這幾組數(shù)據(jù),云測平臺早就可以提供測試了。通過一個(gè)與UI自動(dòng)化并行的線程來發(fā)送adb命令獲取應(yīng)用的性能數(shù)據(jù)。我們需要解決的是如何設(shè)置性能測試的起點(diǎn)和終點(diǎn),還有一個(gè)場景多次重復(fù)測試的問題。
那么我們需要想辦法測試響應(yīng)時(shí)間,我們采用的方案是設(shè)置一個(gè)待加載頁面的基準(zhǔn)控件,利用Appium的元素查找功能一直循環(huán)查找,一旦控件找到了,則認(rèn)為頁面加載成功,這中間的時(shí)間差即為頁面響應(yīng)時(shí)間。這個(gè)時(shí)間雖然沒有代碼插樁準(zhǔn)確,但誤差范圍基本控制在100ms內(nèi),對整體測試結(jié)果的影響不大。
方案實(shí)施
開啟一個(gè)性能測試的線程
關(guān)于獲取方式前面也介紹了,話不多說,直接上代碼:
@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è)置開始和結(jié)束的點(diǎn)
由于UI自動(dòng)化的執(zhí)行每次都是從啟動(dòng)App開始,但性能數(shù)據(jù)的收集卻是針對具體某個(gè)場景的,比如進(jìn)入生活繳費(fèi)頁面。因此為了方便設(shè)置性能測試的開始和結(jié)束時(shí)間,我們在UI自動(dòng)化腳本的錄制action中加了兩個(gè)字段:startPerformance和stopPerformance。用戶可以在腳本的任意位置加入這兩個(gè)action,但主要先后關(guān)系,而且需要配對使用。當(dāng)我們在執(zhí)行腳本的時(shí)候,遇到startPeformance,就啟動(dòng)性能測試線程,然后遇到stopPerformance,則停止線程,并保存數(shù)據(jù)。一個(gè)完整的用于做性能測試的腳本如下所示:

重復(fù)執(zhí)行
對于有些場景,我們?yōu)榱藴p少誤差,通常需要重復(fù)執(zhí)行多次取平均值。因此,我們在新建測試任務(wù)的時(shí)候,增加了設(shè)置重復(fù)次數(shù)的參數(shù)。測試任務(wù)會根據(jù)設(shè)置的參數(shù),重復(fù)執(zhí)行相應(yīng)的次數(shù)。

響應(yīng)時(shí)間的測試
頁面時(shí)間是通過計(jì)算開始加載到某個(gè)關(guān)鍵元素出現(xiàn)的時(shí)間差來作為衡量的,具體實(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 性能測試")){
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ì)
性能測試結(jié)束后,我們將數(shù)據(jù)匯總并進(jìn)行簡單的統(tǒng)計(jì),最終寫入文件,保存最為測試報(bào)告的數(shù)據(jù)支持。這里簡單列舉下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的分布曲線。這里簡單列舉其中兩項(xiàng)數(shù)據(jù)的截圖如下。


總結(jié)
至此,我們通過UI自動(dòng)化與性能測試結(jié)合的方式解決了Android App自動(dòng)化性能測試的問題。而且從腳本的錄制,到測試執(zhí)行,報(bào)告的生成都已經(jīng)平臺化。測試人員只需要錄制非常簡單的腳本,即可一鍵得到相應(yīng)的性能測試報(bào)告。當(dāng)然,這種方式也有其缺點(diǎn),比如響應(yīng)時(shí)間的測試有些小誤差,錄制腳本有一定的學(xué)習(xí)成本。這些都是我們未來要優(yōu)化的方向,歡迎大家提出改進(jìn)建議。