Android穩定性測試-- Monkey二次開發

CharliChen發表於2017-01-23

引子

前面一遍blog Monkey原始碼分析講到Monkey的程式碼結構和程式碼執行流程,相信通過介紹大家應該對monkey的執行原理和核心邏輯有了很深刻的瞭解。我們做的這一切都是為了更好的瞭解monkey的內部邏輯進行二次開發。
為什麼要二次開發前面的文章也大概說了,它畢竟是一款為穩定性測試而準備的小工具,所以存在很多侷限性:

  • Monkey不提供截圖功能,因此測試很難找到問題復現的場景;
  • Monkey無法進行控制元件識別,對事件流控制能力很微弱;
  • 執行過程中容易誤點工具欄導致Wi-Fi關閉,影響測試效果;等等。

本節重點介紹的就是如何通過Monkey原始碼改造的方法來解決上述問題,以更好地提升Monkey的使用效果。
1.保持隨機
2.儘可能讓其他控制元件有相同機會
3.儘可能操作有意義的控制元件
4.儘可能覆蓋到每個Activity

Monkey程式碼重編譯

monkey.jar的原始碼位於Android原始碼的
\development\cmds\monkey\src\com\android\commands\monkey 目錄下,如圖所示
這裡寫圖片描述


Monkey重編譯的方法有兩種:

  • 一種是在Linux環境下編譯
  • 另一種是在Windows環境下編譯

因為在Windows環境下編譯更為常見,所以這裡會重點介紹第二種方法。

Linux環境下編譯

在Linux環境下,下載要測試Android系統版本對應的全部原始碼,進入原始碼目錄。

  • 執行.build/envsetup.sh,設定Android的編譯環境
  • 執行make monkey開始編譯Monkey
  • 編譯成功後,可在/out/target/product/generic/system/framework/中獲取Monkey.jar包。

Windows環境下編譯

Windows環境下的編譯要稍微複雜一點。

  • 建立Monkey專案。同樣也是需要下載要測試Android系統版本對應的全部原始碼,在/development/cmds/monkey目錄下找到Monkey的工程原始碼。在Eclipse中新建一個Java工程,把Monkey原始碼匯入進去
  • 設定Java Build Path。選中對應專案,在頂部選單欄依次選題Project→Properties→Jave Build Path→Libraries,新增兩個jar檔案:android.jar和framework.jar。

其中android.jar可以從Android Sdk中platforms\android-X\目錄下獲取;framework.jar可以通過以下兩種方式獲取。
(1)(推薦)從在Linux環境下Android原始碼根目錄執行make update-api編譯生成,如截圖中的classes_dex2jar.jar檔案就是通過Android原始碼編譯生成的。
(2)直接從Android手機上/system/framework目錄下獲取已經編譯好的framework.jar檔案,把這個framework.jar解壓,取出其dex,然後把它的dex通過dex2jar工具轉換為jar包,匯入工程。新增android和framework的jar包後,還需要將framework的jar包順序調整到頂部,如圖所示:
這裡寫圖片描述

  • 編譯生成jar包。選擇Monkey專案,單擊右鍵→單擊Export→選擇輸出的Jar包型別為“JAR file”類,單擊“Next”按鈕;選擇對應的構建工程,填寫jar包輸出路徑,單擊“Next”按鈕;進入打包選項頁面,這裡用預設選項即可,直接單擊“Next”按鈕;
    選擇工程中main函式所在的類,單擊“Finish”按鈕;編譯完成後,在指定目錄下就會生成對應的Monkey.jar包了。

  • 轉換Monkey.jar包。Eclipse編譯出來的jar包是不能直接放到Android手機上執行的,在Android上無法像Java中那樣方便地動態載入jar。原因是:Android的虛擬機器(Dalvik VM)是不能識別Java打出的jar的byte code的,這裡需要通過Android sdk中的dx工具來優化轉換成Dalvik byte code才行。

將打包好的jar複製到SDK安裝目錄android-sdk-windows\platform-tools下,開啟命令列進入platform-tools目錄,執行命令:
dx –dex –output=<生成的目標檔案> <要轉換的檔案>

重編譯的包執行方法

要執行重編譯後的Monkey.jar有以下兩個前提條件。
·手機擁有root許可權。
·手機Android版本與Monkey.jar包的Android版本一致。
(由於不同版本的Android系統API不同,因此不同版本的Monkey包也是不能通用的。例如:Android 4.2版本的Monkey只能在Android 4.2的系統上執行。)

步驟1:建立啟動shell指令碼。
在本地新建一個用於啟動Monkey的shell指令碼,輸入以下命令,並儲存成Monkey。這個檔案是用來啟動和執行Monkey.jar的,如下面的程式碼所示。

# Script to start "monkeytest"on the device, which has a very rudimentary
# shell.
#這裡要填寫編譯後生成的jar檔名稱
export CLASSPATH=/data/ Monkey.jar
#這裡要填寫jar檔案中的入口函式所在類
exec app_process /data com.android.debug.monkey.Monkey $*

步驟2:上傳指令碼和jar包到手機。
將步驟1建立的Monkey指令碼和Monkey.jar包上傳到手機的/data/loal目錄(可自己定義,與shell指令碼中的目錄一致即可),並將Monkey檔案修改成可執行許可權,如下面程式碼所示。
adb push Monkey.jar /data
adb push monkey /data
adb shell chmod777 /data/monkey
個別手機上執行chmod命令時會報Segmentation Fault錯誤,這時可以先adb shell進入,通過sw root命令切換到root下,再執行chrnod 777/data/monkey即可。

步驟3:執行monkey。
通過命令列視窗,輸入 adb shell./data/local/monkey 命令啟動Monkey.jar包即可執行Monkey。


Monkey原始碼改造

程式碼目錄如下:

這裡寫圖片描述

1. 截圖

掌握重編譯Monkey的方法後,接下來要開始進行Monkey原始碼改造了。第一個改造就是截圖改造。Monkey使用過程中最大的難題就是如何獲取異常出現的場景。雖然Monkey在執行過程中提供了日誌來記錄事件執行順序,但是光靠日誌來定位異常出現的場景並復現它是非常困難的。當Monkey執行過程中出現異常時,若可以對應進行截圖並記錄異常出現前執行的操作,就可以清晰地知道異常出現的場景,也便於定位和解決問題。

具體改造方法如下:
測試期望實現的是在每個事件執行過程中增加截圖並在圖片上畫出事件軌跡。這裡以螢幕觸控操作為例,首先找到觸控事件所在的檔案MonkeyMotionEvent.java,找到負責執行該事件的injectMotionEvent方法。程式碼清單如下。

public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
        MotionEvent me = getEvent();
        if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
            StringBuilder msg = new StringBuilder(":Sending ");
            msg.append(getTypeLabel()).append(" (");
            switch (me.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    msg.append("ACTION_DOWN");

                    //截圖
                    ImageUtils.takeScreenshot();
                    //獲取當前點選座標,存到佇列中
                    ImageUtils.addPoint(me.getX(),me.getY());                         
                    break;
                case MotionEvent.ACTION_UP:
                    msg.append("ACTION_UP");
                    //獲取當前點選座標,存到佇列中
                    ImageUtils.addPoint(me.getX(),me.getY());
                    //把佇列中的點選座標畫到圖片上
                    Bitmap bc=ImageUtils.drawPoint(ImageUtils.scaleBitmap());
                    //bc=ImageUtils.lessenBitmap(bc,0.6f);//等比壓縮
                    //bc=ImageUtils.zoomBitmap(bc,400);//等高壓縮
                    ImageUtils.saveBitmap(bc);//儲存圖片
                    //清空佇列
                    ImageUtils.removePointList();
                    break;

                case MotionEvent.ACTION_MOVE:
                    msg.append("ACTION_MOVE");
                    //獲取當前點選座標,存到佇列中
                    ImageUtils.addPoint(me.getX(),me.getY());
                    break;
                case MotionEvent.ACTION_CANCEL:
                    msg.append("ACTION_CANCEL");
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
                    break;
                default:
                    msg.append(me.getAction());
                    break;
            }
            msg.append("):");

            int pointerCount = me.getPointerCount();
            for (int i = 0; i < pointerCount; i++) {
                msg.append(" ").append(me.getPointerId(i));
                msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
            }
            System.out.println(msg.toString());
        }
        try {
            if (!InputManager.getInstance().injectInputEvent(me,
                    InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
                return MonkeyEvent.INJECT_FAIL;
            }
        } finally {
            me.recycle();
        }
        return MonkeyEvent.INJECT_SUCCESS;
    }

參照上面的修改思路,將Monkey的其他方法也進行類似的修改。這樣,Monkey每執行一個操作,系統就會自動對其進行截圖描點了。

2.Wi-Fi自動重連優化

我們知道大部分的應用程式是需要聯網的,假如Monkey在執行過程中Wi-Fi斷開了怎麼辦?由於Monkey執行的是隨機事件流,過程中的操作無法控制,使用者很容易誤點到工具欄而導致Wi-Fi斷開。對於需要聯網的應用,當Wi-Fi斷開後,很多頁面都會無法開啟,此時Monkey執行的效果會相當不理想。相信這也是絕大多數使用者遇到的問題,當前小節介紹的就是如何通過Monkey改造來實現Wi-Fi斷開重連的功能。
首先,新增一個用於Wi-Fi監控的事件MonkeyWifiEvent。在Monkey中新增一類事件有以下兩個步驟。

  • 在MonkeyEvent新增一個eventType型別,如程式碼清單所示
publicabstractclassMonkeyEvent {
protectedinteventType;
publicstaticfinalintEVENT_TYPE_KEY = 0;
publicstaticfinalintEVENT_TYPE_TOUCH = 1;
publicstaticfinalintEVENT_TYPE_TRACKBALL = 2;
publicstaticfinalintEVENT_TYPE_ROTATION = 3;  // Screen rotation
publicstaticfinalintEVENT_TYPE_ACTIVITY = 4;
publicstaticfinalintEVENT_TYPE_FLIP = 5;
publicstaticfinalintEVENT_TYPE_THROTTLE = 6;
publicstaticfinalintEVENT_TYPE_NOOP = 7;
#新增一個Wi-Fi監控的事件型別
publicstaticfinalintEVENT_TYPE_WifiCheck = 9;
…
  • 新增對應事件的MonkeyWifiEvent類,需繼承自MonkeyEvent類,程式碼清單所示:
publicclassMonkeyWifiEvent extendsMonkeyEvent{
//初始方法
publicMonkeyWifiEvent() {
    super(MonkeyEvent.EVENT_TYPE_WifiCheck);
}
//呼叫CheckWifiConnection()方法檢查Wi-Fi連線
publicintinjectEvent(IWindowManager iwm, IActivityManager iam,intverbose){
    System.out.println("Check Wifi Conection.");
    wifiManager.CheckWifiConnection();
    returnMonkeyEvent.INJECT_SUCCESS;
}

從上面的程式碼可以看到,該事件是通過呼叫CheckWifiConnection()方法來檢查Wi-Fi連線並自動重連的。CheckWifiConnection()方法的實現很簡單,首先初始化一個WifiManager的物件,呼叫其getWifiEnabledState方法,檢查當前Wi-Fi是否連線,當判斷為Wi-Fi無連線時,呼叫setWifiEnabled方法開啟Wi-Fi。等待Wi-Fi開啟後,通過getConfiguredNetworks方法獲取Wi-Fi列表,並遍歷列表查詢需要連線的Wi-Fi的SSID。查詢到後,連線到對應的Wi-Fi上。具體實現如程式碼如下:

publicstaticvoidCheckWifiConnection(){
    IWifiManager im=IWifiManager.Stub.asInterface(ServiceManager
    .getService("wifi"));
    try{
        intstate=im.getWifiEnabledState();
        System.out.println(state);
        WifiInfo wi=im.getConnectionInfo();
        if(state!=3){
        //開啟Wi-Fi
        System.out.println("Wifi not conect, connecting wifi.");
        im.setWifiEnabled(true);
        //等待Wi-Fi開啟,然後連線freewifi
        for(inti=0;i<90;i++){
            if(im.getWifiEnabledState()==3){
            //連線freewifi
            List t=im.getConfiguredNetworks();
            if(t!=null){
                for(int j=0;j
                if(t.get(j).SSID.indexOf("Tencent-FreeWiFi")!=-1){
                intnetworkid=t.get(j).networkId;
                im.enableNetwork(networkid, true);
                Thread.sleep(7000);
                }
                break;
            }
        }
        break;
        }else{
            hread.sleep(2000);
        }
    } catch(RemoteException e) {
        e.printStackTrace();
    } catch(InterruptedException e) {
        e.printStackTrace();
    }catch(SecurityException e) {
        e.printStackTrace();
    }
}

前面說的需求是實現定時監控,所以需要在Monkey.java中的runMonkeyCycles()下每隔1000個事件就插入一個Wi-Fi監控事件,實現如程式碼清單如下:

privateintrunMonkeyCycles() {
    inteventCounter = 0;
    intcycleCounter = 0;
    booleanshouldReportAnrTraces = false;
    booleanshouldReportDumpsysMemInfo = false;
    booleanshouldAbort = false;
    booleansystemCrashed = false;
    // TO DO : The count should apply to each of the script file.
    while(!systemCrashed && cycleCounter < mCount) {
        ...
        //新增Wi-Fi檢查的事件—sharon
        if(cycleCounter%1000==0){
            try{
                addWifiEvent();
            } catch(RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    System.out.println("Events injected: "+ eventCounter);
    returneventCounter;
}

這樣,當Monkey每執行完1000個事件後,就會去檢測一下Wi-Fi的連線狀態,當發現Wi-Fi斷開就會自動重連。重新編譯一下Monkey,然後看一下效果,當Monkey檢查到Wi-Fi斷開會自動重連。

小結

Monkey是Android測試中常用的一個穩定性測試工具,掌握Monkey工具本身的使用方法是非常簡單的。但是真正能深入瞭解Monkey的程式碼實現邏輯,並且具備優化Monkey能力,還是需要一定難度。通過本blog,大家應該學習到Monkey的一些基本知識和基本使用方法,還可以通過對Monkey程式碼邏輯和擴充套件例項的學習,有所啟發,掌握新的自動化測試的方案。

上面我說分析的只是截圖功能和wifi重連功能,還有其他的功能,大家都可以去嘗試開發,由於篇幅太長,我就不一一闡述啦,有問題隨時留言,或加我微信一起探討。。

這裡寫圖片描述

相關文章