MonkeyRunnerとuiautomator

最近のAndroidでは、標準で使える(楽しい)テストツールもだんだんと充実してきました。今回は、そのなかで自動試験に使えそうな、MonkeyRunnerとuiautomatorを簡単に紹介します。
私はPython使いなので、uiautomatorもPythonバインディングの方しか知りません・・・。(Javaでの使い方は他のページもあるし、そちらをみてください)

※重要な心得※
自動試験は手数を減らすのが目的です。
手数が減らないようなものを自動試験しても仕方がないですし、目的が不明瞭なものはかえって自動化しようとしてできなくてハマります。
私の経験上、「なにか定型的な作業やってるなぁ」と感じた時こそが自動化のはじまりです。


・MonkeyRunner
概念的には

 PC…adb接続…→[tcpport:5555 Android端末]

てなかんじで、ソケット通信で命令をうじゃうじゃ流し込んで端末をあれこれ操作します。

PC側主体で端末をうじゃうじゃ動かす形式ゆえ、後述するuiautomatorのように、画面の作りを意識したような試験(「OKボタンをおす」とか「メニューの2番めの項目を選ぶ」とか)には向いていません。

逆に、画面の作りに依存しない試験についてはuiautomatorより簡単にかけます。

ドキュメントはおもに↓を見ておけばよいでしょう。(MonkeyRunnerクラスは、かなり特殊なケースでしか使わないので)
http://developer.android.com/tools/help/MonkeyDevice.html

で、いきなりコードをかいちゃいましょう。
中身はそれなりに読めばわかるでしょう。

# -*- coding:utf-8 -*-

# 決まり文句
import time
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
d = MonkeyRunner.waitForConnection(deviceId="EP7331C9G7")

# screen on
d.wake()
time.sleep(1)

# swipe up for unlock
x=int(d.getProperty("display.width"),10)/2
y1=int(d.getProperty("display.height"),10)/4
y0=y1*3
d.drag((x,y0),(x,y1),0.2,10)
time.sleep(1)

# launch google chrome
d.press("KEYCODE_HOME",MonkeyDevice.DOWN_AND_UP)
time.sleep(1)
d.shell("am start -n com.android.chrome/com.google.android.apps.chrome.Main")
time.sleep(1)

# browse yahoo.com
d.press("KEYCODE_SEARCH",MonkeyDevice.DOWN_AND_UP)
time.sleep(1)
for i in xrange(200):
d.press("KEYCODE_DEL",MonkeyDevice.DOWN_AND_UP)
d.type("http://www.yahoo.com/")
time.sleep(1)
d.press("KEYCODE_ENTER",MonkeyDevice.DOWN_AND_UP)
time.sleep(0.2)
d.press("KEYCODE_ENTER",MonkeyDevice.DOWN_AND_UP)
time.sleep(5)

# swipe up 3 times
y0=y1*2
for i in xrange(3):
d.drag((x,y0),(x,y1),0.2,10)
time.sleep(0.5)
time.sleep(3)

# swipe up 3 times
y0=y1*2
for i in xrange(3):
d.drag((x,y1),(x,y0),0.2,10)
time.sleep(0.5)
time.sleep(3)

# return to HOME
d.press("KEYCODE_HOME",MonkeyDevice.DOWN_AND_UP)
time.sleep(2)

d.press("KEYCODE_POWER",MonkeyDevice.DOWN_AND_UP)
time.sleep(1)

フリックはswipeとか気の利いたメソッドはないので、dragメソッドで点の数をうまく調整して実行してやる必要があります。
VSYNCの周期が60Hzなので、それを意識して1点あたり16msくらいになるようにdurationとstepを決めてやらないと、美しいスワイプ動作にはなりません。

これとは別にadb logcatを監視するスレッドを作ってやれば、
logcatに変なエラーが出た時だけそのスクリーンショットを残す、とか気の利いたことができます。
logcat監視スレッドはこのあたりのソースを拝借して改変すれば割と簡単に作れます。

繰り返し試験をやるときの注意ですが、これ結構重要で、
間違っても
for i in xrange(1000):
print i,"回目のテスト"
doTest()
なんてやってはいけません。
17回目くらいの試験でたまたま友達からのメールがとどいて、思ったように操作ができずMonkeyRunnerがエラーになったりしたら、そこで自動試験が終わっちゃいます。
17回目の試験でコケても18回目、それでコケても19回目、とやっていってもらわないと困りますね。
import os,sys
if sys.argv==1:
for i in xrange(1000):
os.system("monkeyrunner %s %d"%(sys.argv[0],d))
else:
i=int(sys.argv[1],10)
print i,"回目のテスト"
doTest()
荒業ではありますが、こう書いたほうがはるかに頑健な自動試験スクリプトとなります。


・uiautomator
概念的にはMonkeyRunnerとは対照的で

 PC…実行プログラムを転送→[Android端末]

てなかんじで、Android端末側が自主的に、書かれた命令をうじゃうじゃ実行します。
画面を意識した試験を行うための命令セットもたくさん用意されているので、UIを中心に試験するなら間違いなくこっちでしょう。

Pythonバインディングのインストールは
sudo apt-get install python-pip
sudo pip install uiautomator

ちなみに、ドキュメント(※1)はあまり充実していないので、ソースコード(※2)とAndroidのリファレンス(※3)をページ内検索したり行ったり来たりで読むのがいいと思います。
※1 https://github.com/xiaocong/uiautomator の下の方
※2 https://github.com/xiaocong/uiautomator/blob/master/uiautomator.py
※3 http://developer.android.com/tools/help/uiautomator/UiDevice.html

あ、ちなみにWindowsじゃMonkeyRunnnerは動かない(らしい)ですが、uiautomatorは動きます!私はLinux使うのであんまり気にしてないですが、会社とかだとWindowsオンリー!っていうところもあるでしょうから。

WindowsでnumpyとかMatPlotlibとかひと通り入れた状態でのPython3環境ではありますが、以下のようにすればuiautomatorをインストールせずとも、味見程度に動かすことはできます。


mkdir uiautomator_test
cd uiautomator_test

# urllib3をおとしてくる
git clone https://github.com/shazow/urllib3.git
mv urllib3 _urllib3
mv _urllib3\ullib3 urllib3

# uiautomatorを落としてくる
git clone https://github.com/xiaocong/uiautomator.git

touch mytest.py
explorer .

C:\Users\yi01\Desktop\uiautomator_test>ls
_urllib3 mytest.py uiautomator urllib3
こんなかんじのフォルダ構成になったらmytest.pyにスクリプトをゴニョゴニョっと書いていきます。あ、あと一応adb接続でいろいろ送り込むので、以下のようになってる前提です。
C:\Users\yi01>adb devices
List of devices attached
EP7331C9G7 device

こちらもいきなりコードからですが、設定画面を開いて「マップ」のデータ全削除未遂をするという糞シナリオです。
設定画面のなかで「アプリ」というのはどこにあるか、座標的にはわかりませんよね?そういうMonkeyRunnerでは痒いところに手が届かない!というのが、uiautomatorでは手が届いちゃうんです。
# -*-codoing:utf-8 -*-

# 決まり文句
from uiautomator.uiautomator import device as d

#デバイスID指定でやりたいときは↓
#from uiautomator.uiautomator import Device
#d=Device("EP7331C9G7")

import time

for i in range(3):
print("画面よ、つけー!")
d.screen.on()
time.sleep(1)

print("画面よ、消えろー!")
d.screen.off()
time.sleep(1)

print("画面よ、つけー!")
d.screen.on()
time.sleep(2)

print("スワイプして画面ロック解除するぞ")
x=d.displayWidth/2
y0=d.displayHeight/2
y1=y0/2
y0+=y1/2
d.swipe(x, y0, x, y1, steps=12)
time.sleep(1)

print("HOME")
d.press.home()
time.sleep(1)


#リファレンスには書いてないけどソースを見るとadbも使えるっぽい。
def adb_shell(device,*cmd):
c=["shell"]
c.extend(cmd)
print (" ".join(c))
return [s.decode("utf-8") for s in device.server.adb.cmd(*c).communicate()]

print("設定アプリを立ち上げよう")
adb_shell(d,"am start com.android.settings/.Settings")
time.sleep(2)


d(text="アプリ").click()
time.sleep(3)

d(text="マップ").click()
time.sleep(3)

d(text="データを削除").click()
time.sleep(2)

print("イッヒッヒ!!")

d(text="キャンセル").click()
time.sleep(2)

print("やさしいから消さないもーん")

d.press.home()
time.sleep(2)

print("ほな、さいなら~")
time.sleep(2)

むちゃくちゃ手抜きですけど、こんな具合になります。↓

これだけだと、はて何に使うんかいね?って感じがすると思いますが、
d(text="hogehoge") 以外にもセレクタの条件いろいろ使えるみたいです。

  • texttextContainstextMatchestextStartsWith
  • classNameclassNameMatches
  • descriptiondescriptionContainsdescriptionMatchesdescriptionStartsWith
  • checkablecheckedclickablelongClickable
  • scrollableenabled,focusablefocusedselected
  • packageNamepackageNameMatches
  • resourceIdresourceIdMatches
  • indexinstance
↑公式リファレンスからイタダキしました。

resourceIdとか、自分の作ったアプリ以外だとわかんないじゃん!と思うかもしれません。
そんな人のためにも、Androidはすばらしいツールを用意してくれています。

uiautomatorviewer

FirefoxでいうDOMインスペクタ的なものです。
画面のスクリーンショット上で、この要素のIDなぁに?パッケージ名は?などなど
けっこういろいろ見えます。

使い方は
http://developer.android.com/tools/testing/testing_ui.html
ここに詳しく載ってるのでそちらを・・・。


そんなわけで、
ともかくも、活かすも殺すも、発想次第!

自動試験は思い通りに行かないことも多く、作るのに結構時間はかかりますが、
一度作ってしまえば終夜試験で大幅に工数削減とか、繰り返し試験で耐久性向上!なんてことがいとも簡単にできるようになります。

私も、半年前くらいから、自動試験の虜になってしまいました^ ^;;
さあ、ぜひぜひおためしあれ。