當前位置:
首頁 > 最新 > 用Instrumentation改良monkey工具實戰

用Instrumentation改良monkey工具實戰

這裡Monkey不是猴子,而是Android系統中用來做自動化測試的工具,即盲點、壓力測試。

在之前的移動端產品迭代中,Monkey工具一直沒有利用起來。開發同學忙於需求,測試同學資源較少,自動化測試工具欠缺,重視不夠。版本發布的流程,壓力測試這一環節是完全缺失的。crash沒有在發版前提前發現,也造成我們線上產品crash率較高。

App不同於H5,一旦發布版本,其更新成本、周期是比較高的。所以應當將發版前的質量保證作為第一要務,確保可靠性。

1. 問題及分析1.1 現象

monkey工具的用法,網上有很多資料,在此不作介紹。可參考:UI/Application Exerciser Monkey

用法很簡單。但是,我們在初步使用monkey的過程中,幾乎必然進入一個較深的路徑中,再也無法跳出來——可能是在兩個頁面、或者Dialog、Input面板間不斷的切換,始終沒法關閉頁面,逐級跳出。在我測試的過程中,發現幾乎都是進入了一個webview頁面:

monkey走入了死胡同,一直在一個小圈子裡、幾個頁面間打轉,無法發揮作用。

1.2 探索

monkey的實現原理,參考源碼:monkey

當敲下 實際是通過執行一段shell腳本,啟動monkey.jar。入口在Monkey.java:main()方法當中。

通過調整–pct-touch, –pct-motion, –pct-syskeys, –pct-appswitch等參數比例,monkey會隨機生成相應事件(MonkeySourceRandom.java::generateEvents()):

monkey產生touch事件的坐標位置是完全隨機的(MonkeySourceRandom.java::generateMotionEvent()):

1.3 結論分析

所以,到這裡,基本上可以對上面的問題做一個解答,即:為什麼monkey會進入幾個頁面後無法跳出?

有以下幾點:

touch事件點擊的位置是全屏幕隨機的;

webview中頁面幾乎是每個地方都可以點擊,並且點擊後跳到另一個頁面;

雖然頁面左上角有返回鍵、也有物理Back鍵,但是返回鍵所佔的區域只是屏幕上很小一部分,大約只佔屏幕點擊事件總數的1/80(按面積計算), 物理Back鍵也只佔所有SYS_KEYS中的1/7。這裡多麼類似於生物蟻群演算法,進入死循環就彷彿是找到了最短路徑。但遺憾的是,monkey的目的是希望能夠最大程度覆蓋所有可能的執行路徑。繼續進入下一個頁面的可能性永遠比退出去更多,除非這個頁面的有效點擊區域變小才能增大退出來的可能性。

有贊微商城App中一個典型的webview頁面:

2. 解決方案

如果監聽每個activity的啟動過程,並且判斷它的存活時間,當認為已經太長了,主動將其finish掉。這似乎是個可行的方案。由此想到用Instrumentation, 通過Instrumentaion啟動App,再開啟monkey測試,不就能控制頁面深度及存活時間。

這裡需要特別注意的是:關閉activity的策略,該如何定製?如果策略不合理,很可能造成

比較深的頁面跑不到;

單頁面的點擊,測試完整度不夠

目前我所使用的策略是:

topActivity,沒有切換的情況下,最長存活時間為15s

當前Activity棧中,從上往下,第一層存活時間30s,每層遞增30s,超過時間後依次finish彈出

每個task最長存活時間10分鐘

MonkeyInstrumentation源碼附上:

public class MonkeyInstrumentationextends Instrumentation{ private static final String TAG = "MONKEY_INSTRUMENT"; // config params private long checkTaskInterval = 5000; // 5s private long topActivitySurvivalTime = 15*1000; // 15s private long stackActivitySurvivalTimeFirstLevel = 30*1000; // 30s private long stackActivitySurvivalTimeIncremental = 30*1000; // 30s private long taskSurvivalTime = 10*60*1000; // 10min private Handler handler = null; private ActivityManager activityManager = null; private List activityList = null; private SparseArray survivalTimeMap = null; private Activity currentActivity = null; private long currentActivitySurvivalTime = 0; private SparseArray taskSurvivalTimeMap = null; public MonkeyInstrumentation(){ super(); } @Override public void callApplicationOnCreate(Application app){ super.callApplicationOnCreate(app); handler = new Handler(); activityList = new ArrayList(); survivalTimeMap = new SparseArray(); taskSurvivalTimeMap = new SparseArray(); Log.e(TAG, "call application on create, app:" + app); postCheckTask(); } @Override public void callActivityOnCreate(finalActivity activity, Bundle icicle){ super.callActivityOnCreate(activity, icicle); int index = activityList.size(); activityList.add(activity); long now = System.currentTimeMillis(); survivalTimeMap.put(index, now); int taskId = activity.getTaskId(); Log.e(TAG, "create activity, activity:" + activity + ", taskId:" + taskId + ", index:" + index + ", now:" + now); if (taskSurvivalTimeMap.get(taskId, 0L) == 0) { taskSurvivalTimeMap.put(taskId, now); } } @Override public void callActivityOnResume(Activity activity){ super.callActivityOnResume(activity); currentActivity = activity; currentActivitySurvivalTime = System.currentTimeMillis(); } @Override public void callActivityOnPause(Activity activity){ super.callActivityOnPause(activity); } @Override public void callActivityOnDestroy(finalActivity activity){ super.callActivityOnDestroy(activity); int index = activityList.indexOf(activity); if (index >= 0) { activityList.remove(index); survivalTimeMap.remove(index); } } private void postCheckTask(){ handler.postDelayed(new Runnable() { @Override public void run(){ Log.e(TAG, "post check task run"); checkActivityStatus(); postCheckTask(); } }, checkTaskInterval); } private void checkActivityStatus(){ Log.e(TAG, "to checkActivityStatus"); checkCurrentActivity(); checkStackActivity(); checkCurrentStack(); } private void checkCurrentActivity(){ Log.e(TAG, "checkCurrentActivity"); if (currentActivity != null){ if (System.currentTimeMillis() - currentActivitySurvivalTime > topActivitySurvivalTime) { // 15s Log.e(TAG, "checkCurrentActivity, to finish a long time activity:" + currentActivity); currentActivity.finish(); currentActivity = null; currentActivitySurvivalTime = 0; } } } private void checkCurrentStack(){ Log.e(TAG, "checkCurrentStack"); if (activityManager == null) { Context context = getContext(); if (context != null) { activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); } } if (activityManager != null) { long now = System.currentTimeMillis(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { List appTaskList = activityManager.getAppTasks(); if (appTaskList != null && appTaskList.size() > 0) { ActivityManager.AppTask appTask = appTaskList.get(0); int taskId = appTask.getTaskInfo().id; Long taskTime = taskSurvivalTimeMap.get(taskId); if (taskTime != null && now - taskTime > taskSurvivalTime) { Log.e(TAG, "finish and remove appTask:" + appTask); for (int i = activityList.size() - 1; i >= 0; --i) { if (activityList.get(i).getTaskId() == taskId) { activityList.remove(i); survivalTimeMap.remove(i); } } appTask.finishAndRemoveTask(); } } } else { List runningTaskInfoList = activityManager.getRunningTasks(1); if (runningTaskInfoList != null && runningTaskInfoList.size() > 0) { ActivityManager.RunningTaskInfo runningTaskInfo = runningTaskInfoList.get(0); int taskId = runningTaskInfo.id; Long taskTime = taskSurvivalTimeMap.get(taskId); if (taskTime != null && now - taskTime > taskSurvivalTime) { Log.e(TAG, "finish and remove runningTask:" + runningTaskInfo); for (int i = activityList.size(); i >= 0; --i) { Activity activity = activityList.get(i); if (activity.getTaskId() == taskId) { activityList.remove(i); survivalTimeMap.remove(i); activity.finish(); } } } } } } else { Log.e(TAG, "checkActivityStatus, activityManager is null"); } } private void checkStackActivity(){ Log.e(TAG, "checkStackActivity"); int len = activityList.size(); long time = stackActivitySurvivalTimeFirstLevel; long now = System.currentTimeMillis(); Activity needClearActivity = null; for (int i = len - 1; i > 0; --i) { if (now - survivalTimeMap.get(i, 0L) > time) { needClearActivity = activityList.get(i); break; } time += stackActivitySurvivalTimeIncremental; // increment every level } if (needClearActivity != null) { Log.e(TAG, "needClearActivity:" + needClearActivity); // to clear activity above needClearActivty in this task int id = needClearActivity.getTaskId(); for (int i = len - 1; i > 0; --i) { Activity activity = activityList.get(i); if (activity.getTaskId() == id) { Log.e(TAG, "clearStackActivity, activity:" + activity); activityList.remove(i); survivalTimeMap.remove(i); activity.finish(); } } } } }3. 使用

將 MonkeyInstrumentation集成進App項目代碼中,並在AndroidManifest.xml中聲明

其中 MONKEY_TEST_PACKAGE 為待測包名,另注意修改MonkeyInstrumentaion所在包名。

編譯安裝好Apk

啟動instrumentation, 目標進程啟動並監聽activity棧存活狀態

adb shell am instrument MONKEY_TEST_PACKAGE/RUNNER_CLASS

其中RUNNER_CLASS即為MonkeyInstrumentation

啟動monkey測試

adb shell monkey -p MONKEY_TEST_PACKAGE --throttle 300 --pct-touch 60 --pct-motion 15 --pct-syskeys 10 --pct-appswitch 15 -s `date +%H%M%S` -v -v -v --monitor-native-crashes --ignore-timeouts --hprof --bugreport COUNT > monkey_test.txt

結果查看

4. 總結

monkey這個工具,看起來很簡單,但使用起來還是會遇到這樣的坑。以前有專職的測試同學替我們完成monkey,測試,導致對遇到的問題也沒有去深究。

發版前的自動化測試,包括UT、UI測試、monkey、內存、性能及流暢度、Apk Size等等,越來越成為上線發版流程中不可或缺的一環,我們在不斷的建設完善當中。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

理解高斯混合模型
PHP_設計模式
時下流行的側邊欄導航到底好不好用?
我來談談PHP和JAVA的區別
小團隊中人人都要會用點Docker

TAG:推酷 |

您可能感興趣

Elizabeth Olsen 希望 Marvel 能為 Scarlet Witch 改良戰衣
改良版本,Nike React Runner SP Mid SOE 曝光
【Latest Science】科學家用「基因剪刀」改良香蕉
細節配色大改良!新版Air Jordan 3 Tinker「黑水泥」近賞!
Balenciaga Triple S 改良版正式上架!原價擺在你面前你的選擇是?
蘋果蓄力中,iPhonePLUS將會是「絕招」改良iPhone的弊端!
新品三葉草adidas Crazy 1 ADV Primeknit 改良瘋狂一代針織「午夜深藍桃粉」CQ0982
三星重推改良版Galaxy Fold:被傷透的T-Mobile表示拒絕引進
品牌推出全新改良版男士香水 Polo Ultra Blue
蘋果發布新款MacBook Pro 改良蝶式鍵盤
新款MacBook Air應該如何改良?無邊框+劉海設計
MUJI Hotel:被改良的酒店生意
三星高管:目前尚無改良版Galaxy Fold發布時間信息
減少副作用!Nature醫學:改良版CAR-T更安全有效
三星正式宣布了Galaxy Fold改良版的發售日期
糾正疾病!兩篇Nature醫學揭示:改良版CRISPR新進展
糾正疾病!兩篇Nature醫學揭示:改良版CRISPR新進展,有望實現產前編輯
用於腹側疝修復的改良Chevrel技術:單個中心隊列的長期結果
一種改良的CRISPR/Cas9基因編輯方法
Science子刊:改良版血小板可對抗血栓、腫瘤