2014年7月4日

UI Test Script for Android - 以Puzzle And Dragons自動轉珠為範例 (上)


前言

當你每次寫完程式,接著手動測試程式時, 是不是覺得每次都在做同樣的事情而感覺到自己的小宇宙快要被燃燒殆盡了? 在有這種心情產生之後, 公司前輩就會出來對你說:你不是還有肝嗎 寫一下auto test吧 交給你囉~ ^_< 實際上去學一下auto test (例如android上的MonkeyRunner), 大概我是個懶人的關係啦, 單純使用當然是可以用啦, 但是不覺得每次要改個小動作都很麻煩,不直覺嗎? 難道沒有比較簡單一點的東西可以做到auto test嗎?最好是可以簡單到連一般人都會寫這個東西不是很好嗎? 這個答案就是 SIKULI


SIKULI簡介

Sikuli是個新的程式語言概念, 當時看到就覺得是很有趣的東西, 雖然一直都沒有實際嘗試過, 不過感覺上相當友善,跟一般寫程式很大的不同點在於,他可以透過圖片辨識的方式,讓你可以清楚的了解你想操作的物件及動作, 舉個例子來說:
一般程式可能長成這樣:
button = vc.findViewById("id/save_button")
button.touch() 


Sikuli script則是:




是不是看起來簡單很多, 你甚至不需要知道原本在手機裡面按鈕的id叫什麼
引用作者vgod的一段話, 「Sikuli最重要的革命是程式碼的可讀性(readability)和易用性(usability)。」
雖然不清楚對沒寫過程式的人來說是不是馬上就能上手,不過Sikuli IDE設計的也很簡單易懂,對於未接觸過程式的人門檻相對降低了許多

for Android

想要在android上使用Sikuli,目前大部分的作法都是透過pc上的screencast,接著再用原本的方法,抓圖判斷及執行。不過實際上當初在想弄一個auto test script時,雖然知道有Sikuli那個概念的東西,但是我不知道Sikuli的名字,google也沒查到Sikuli(我該學習一下正確的keyword搜尋了)。所以就抱著學習一下的心態,自己就來實作一下android上的auto test script language吧。

首先,先列一下我們需要哪些東西:

1. IDE界面
2. 與android手機端的溝通(screen capture, key/mouse event ... etc)
3. Script language
4. 圖形辨識系統

IDE界面

因為只是個實驗性質的程式, 界面就不要求美觀, 所以就直接用Netbean拉一拉畫面就直接拿來用了, 在這邊就不多說了, 直接看看醜醜的界面吧 XD

與Android手機端的溝通

版本一 - python + java
在最早期為了要快速見到成果,所以與手機端溝通的方法是將script輸出成monkey recorder格式,並且直接餵給monkey runner中的monkey playback來達到執行使用者輸入指令的動作。 以下是執行的python程式:

def execute(device, line):
    line = line.strip()
    if not len(line) or line.startswith('#'):
        return 
    cmd, argv = line.split('|')
    try:
        rest = eval(argv)
    except:
        print("error")
        return
    print('run command=' + cmd)
    CMD_MAP[cmd](device, rest)

def main():
    if len(sys.argv) >= 2:
        command = sys.argv[1]
        device = MonkeyRunner.waitForConnection()
        f = open(sys.argv[2], 'r')
        for line in f.readlines():
            execute(device, line)
        f.close()

不過這個作法對於想要擴充功能來說不是很方便(事實上是對python的熟悉度沒那麼高),加上每次從程式要執行script都需要透過System.exec去執行python,並且waitForConnection(),中間所產生的時間消耗比想像中的來的還要高,所以後來轉向了第二個方法。

版本二 - java + ichimpchat.jar
透過google搜尋了一下,我們可以發現MonkeyRunner底層其實就是透過ChimpChat來實現的,所以我們也可以如法泡製,看一下ChimpChat的source code,可以發現一些常用的touch, press之類的指令,下面為一部分的source code:

public interface IChimpDevice {
    IChimpImage takeSnapshot();

    void touch(int x, int y, TouchPressType type);

    void press(String keyName, TouchPressType type);

    void drag(int startx, int starty, int endx, int endy, int steps, long ms);

    String shell(String cmd);

    boolean installPackage(String path);

    boolean removePackage(String packageName);

    void startActivity(@Nullable String uri, @Nullable String action,
            @Nullable String data, @Nullable String mimeType,
            Collection categories, Map extras, @Nullable String component,
            int flags);

    void broadcastIntent(@Nullable String uri, @Nullable String action,
            @Nullable String data, @Nullable String mimeType,
            Collection categories, Map extras, @Nullable String component,
            int flags);

    Map instrument(String packageName,
            Map args);

    void wake();
}

有了這一個jar檔後,我們就可以來開發java與手機端溝通的程式了,ChimpChat的用法相當簡單:
1. 連結device並取得IChimpDevice的instance

        TreeMap options = new TreeMap();
        options.put("backend", "adb");
        options.put("adbLocation", path);
        mChimpchat = ChimpChat.getInstance(options);
        mDevice = mChimpchat.waitForConnection(TIMEOUT, ".*");

2. 透過IChimpDevice送指令到手機端

// touch x, y
        mDevice.touch(x, y, TouchPressType.DOWN);
// start activity
        String action = "android.intent.action.MAIN";
        Collection< String> categories = new ArrayList< String> ();
        categories.add ("android.intent.category.LAUNCHER");
        String componentName = packageName +"/" + className;
        mDevice.startActivity(null, action, null, null, categories, new HashMap(), componentName, 0);
// Drag
        mDevice.drag(startx, starty, endx, endy, steps, timeMs);

如此一來對手機端ui的操作透過IChimpDevice就能夠簡單的達成了

富奸  待續

沒有留言:

張貼留言