Titanium 製アプリで NativeDriver を試す(Android 編)

Titanium 製 Android アプリで NativeDriver を試したメモ。
(追記)iPhone
Titanium 製アプリで NativeDriver を試す(iPhone 編) - vaccho's blog

NativeDriver とは

NativeDriver とは、アプリを自動操作してテストが行えるツール。
Web アプリに対して Selenium で行うのと同じ。
というか Selenium に統合された WebDriver API を実装している。
http://code.google.com/p/nativedriver/

自動操作されている様子の動画。

すごい。しかも Android/iOS 両対応。
これをぜひ Titanium 製アプリでもやりたい!

Titanium 製アプリで対応する

チュートリアルにあるように、
1. server-standalone.jar を作成してアプリに取り込む
2. アプリインストール後、コマンドを叩いてサーバを起動する
という手順でアプリ側の準備が整う。
http://code.google.com/p/nativedriver/wiki/AndroidMakeAnAppTestable

jar のインポートはどうやったものかと思ったけど、
以前たまたま見つけた Titanium SDK 内に配置して dependency.json に記載する方法でやってみた。
http://vaccho.hatenablog.com/entry/2011/12/19/125712

すると、インポートしようとはしてくれたけど、以下のエラー。。

[WARN] warning: Ignoring InnerClasses attribute for an anonymous inner class
[ERROR] (org.mortbay.jetty.AbstractBuffers$1) that doesn't come with an
[ERROR] associated EnclosingMethod attribute. This class was probably produced by a
[ERROR] compiler that did not target the modern .class file format. The recommended
[ERROR] solution is to recompile the class from source, using an up-to-date compiler
[ERROR] and without specifying any "-target" type options. The consequence of ignoring
[ERROR] this warning is that reflective operations on this class will incorrectly
[ERROR] indicate that it is *not* an inner class.

どうやら、JDK 1.4 以前でビルドした jar が含まれていると発生するらしい。
エラーの内容を見ると
jetty-6.1.22.jar
jetty-util-6.1.22.jar
が原因のようなので、これを手元でビルドする必要がある。

この辺を参考にビルド。
http://docs.codehaus.org/display/JETTY/Building+from+Source
記載通りにやるとエラーになったので必要なものだけ jar を作るように。
jetty-6.1.22.jar なら以下。

$ svn checkout http://svn.codehaus.org/jetty/jetty/tags/jetty-6.1.22/ jetty-6.1.22
$ cd jetty-6.1.22/modules/jetty
$ mvn install

これを NativeDriver の nativedriver/third_party/jetty 以下に持っていって
改めて server-standalone.jar をビルド。

これを使ったらうまくインポートされた。

Titanium 製アプリを操作する

アプリに jar をインポートして、サーバを起動したら、あとはテストコード経由で操作。
サンプルだとこんな風に記載されている。

driver.startActivity("com.google.android.testing.nativedriver"
        + ".simplelayouts.TextValueActivity");

WebElement textEditView = driver.findElement(By.id("EditText01"));
textEditView.sendKyes("test");

Titanium でも AndroidManifest.xml に Activity の名前は定義されているので
これを使うようにすれば良い。
だけど Titanium だと id が設定できないようで、findElement(By.id()) が使えない。
なので ClassNames を使ってみたらうまくいった。

例えば、こんな画面があったとしたら
f:id:vaccho:20120223161722p:plain

こんな感じでメールアドレスとパスワードを入力して、ログインボタンが押せる。

// 入力欄をすべて取得
List<WebElement> textEditViewList = driver.findElements(AndroidNativeBy.className(ClassNames.EDIT_TEXT));

// 一つ目の入力欄がメールアドレス
WebElement mailAddressEditView = textEditViewList.get(0);
mailAddressEditView.click();
mailAddressEditView.sendKeys(mailAddress);

// 二つ目の入力欄がパスワード
WebElement passwordEditView = textEditViewList.get(1);
passwordEditView.click();
passwordEditView.sendKeys(password);

driver.findElement(AndroidNativeBy.className(ClassNames.BUTTON)).click();

ただ、画面の変更に弱い実装になるので、良い方法を模索したいところ。。

ということで

Titanium 製 Android アプリで NativeDriver が利用できた!
iPhone/Android 両対応だとコードを変更したら iPhone/Android 両方で確認しないとなので
こういう自動化テストはすごく役立つはず。。!