AndroidのテストツールNativedriverを使ってみる

サンプルプロジェクトをgithubに上げているので参照してください。
yyhayashi303/Sample_Nativedriver_App · GitHub
yyhayashi303/Sample_Nativedriver_Test · GitHub

環境構築

まずは、GettingStartedAndroid - nativedriver - How to build the NativeDriver server and client libraries and run the sample tests. - Native application GUI automation with extended WebDriver API - Google Project Hostingを見ながら環境構築。

1.jarファイル取得
 ライブラリはjarが配布されているわけではなく、上記のwikiに記載されているsvnからソースを落としてきてantでビルドする必要があります。

$ svn checkout https://nativedriver.googlecode.com/svn/trunk nativedriver --username {Google account e-mail address}
$ cd nativedriver/android
$ ant

 antを実行するとnativedriver/android/build/以下にjarが生成されます。そのうち、使用するのは下記の2つになります。
 ・server-standalone.jar
 ・client-standalone.jar

2.Androidプロジェクト作成
 ・テスト対象となるAndroidプロジェクトを作成し、server-standalone.jarをビルドパスに追加します。
 ・AndroidManifest.xmlに下記を追加

<instrumentation android:targetPackage="sample.nativedriver"
android:name="com.google.android.testing.nativedriver.server.ServerInstrumentation" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

3.テストプロジェクト作成
 ・テストプロジェクトもAndroidプロジェクトと思いきや、通常のJavaプロジェクトを作成。
 ・ビルドパスにclient-standalone.jarとjUnit3を追加します。

以上で準備はOKです。
今回はとりあえず「画面遷移」と「ListViewの操作」をやってみます。

画面遷移のテスト

名前と年齢を入力すると遷移先の画面で「未成年」か「成人」かを判断して出力するActivityを作成します。

  

これをテストするテストクラスを作成します。
まず、コードを少し簡潔にするためにTestCaseクラスを拡張してNativedriverTestCaseクラスを作成しました。
NativedriverTestCase.java

package sample.nativedriver;

import junit.framework.TestCase;
import org.openqa.selenium.By;
import com.google.android.testing.nativedriver.client.AndroidNativeDriver;
import com.google.android.testing.nativedriver.client.AndroidNativeDriverBuilder;
import com.google.android.testing.nativedriver.client.AndroidNativeElement;
import com.google.android.testing.nativedriver.common.AndroidNativeBy;

public abstract class NativedriverTestCase extends TestCase {
	protected AndroidNativeDriver driver;
	@Override
	protected void setUp() throws Exception {
    	driver = new AndroidNativeDriverBuilder().withDefaultServer().build();
	}
	@Override
	protected void tearDown() throws Exception {
		driver.quit();
	}
	protected AndroidNativeElement findElementById(String id) {
		return findElement(By.id(id));
	}
	protected AndroidNativeElement findElementByText(String text) {
		return findElement(AndroidNativeBy.text(text));
	}
	protected AndroidNativeElement findElementByName(String name) {
		return findElement(AndroidNativeBy.name(name));
	}
	protected void startActivity(String activityName) {
		driver.startActivity(activityName);
	}
	protected void back() {
		driver.navigate().back();
	}
	private AndroidNativeElement findElement(By by) {
		return driver.findElement(by);
	}
}

で、これがNativedriverTestCaseを継承したテストクラス
SimpleActivityTest.java

package sample.nativedriver;
import com.google.android.testing.nativedriver.client.AndroidNativeElement;
import com.google.android.testing.nativedriver.common.AndroidKeys;
 
public class SimpleActivityTest extends NativedriverTestCase {

    public void testSubActivity() {
    	startActivity("sample.nativedriver.SimpleActivity");

    	AndroidNativeElement editName = findElementById("edit_name");
    	editName.sendKeys("yyhayashi303");
    	AndroidNativeElement editAge = findElementById("edit_age");
    	// フォーカスを当てるためにクリックする必要がある
    	editAge.click();
    	editAge.sendKeys("27");
    	driver.getKeyboard().sendKeys(AndroidKeys.ENTER);
    	findElementById("transit_next").click();
    	assertEquals("yyhayashi303さんは成人です。", findElementById("profile").getText());
    }
}

コメントにもあるように、click()メソッドを呼んでおかないとeditAge.sendKeys("27")としているのにも関わらず、editNameに入力されてしまうので注意が必要。

テスト実行

実行方法がなかなか面倒で下記の手順で行う必要があります。

1.Androidアプリインストール
 Androidプロジェクトを右クリック → Run As → Android Application

2.テストのリクエストを受けるデバイスの準備
 コンソールから下記のコマンドを実行します。
 sample.nativedriverの部分は、AndroidManifest.xmlに追加したinstrumentationのtargetPackageと同じものを指定します。

adb shell am instrument sample.nativedriver/com.google.android.testing.nativedriver.server.ServerInstrumentation

 NativedriverからのTCP接続を有効化します。

adb forward tcp:54129 tcp:54129

3.テストプロジェクト実行
 テストプロジェクトを右クリック → Run As → JUnit Test

これでテストが実行出来たと思います。
上記手順の2についてですが、テストをしていてアプリが落ちてしまったりした場合は、再度コンソールからコマンドを実行しなければいけないのでとても面倒です。
しかし、Wikiに下記のような記述があり、将来的にはコードから実行できるようになるようです。

In the future these steps should be done through code, maybe using an extended AdbConnection class. For now, this part must be done manually:

ListViewの操作

ListViewのアイテムをクリックすると、その文字が一番したのTextViewに表示されるというものです。

テストクラスは下記になります。
SimpleListActivityTest.java

package sample.nativedriver;

public class SimpleListActivityTest extends NativedriverTestCase {

	public void testOnItemClick() {
		startActivity("sample.nativedriver.SimpleListActivity");

		String[] array = new String[] {
				"java",
				"javascript",
				"python",
				"ruby",
				"perl",
				"scala"};
		for (String text : array) {
			// 指定リストをクリック
			findElementById("list").findElementByText(text).click();
			// 少し待たないと下記のチェックでselectedの値が取得できない...
			w(200);
			// 選択されたアイテムが正しいかチェック
			assertEquals(text, findElementById("selected").getText());
		}
	}

	private void w(long time) {
		synchronized (this) {
			try {
				wait(time);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
	}
}

ListViewの場合、各アイテムにidが無いのでtextとして設定されている文字列でAndroidNativeElementを取得するしかなく、汎用的にテストを書くことができませんでした。。
ListViewの全要素を取得できるような方法を知っている方がいたら教えていただきたいです。
それと、各アイテムのclick()メソッドを呼び出した直後にselectedのgetText()を呼び出すと、まだ値が設定されていなくてテストがエラーになってしまうので、200ミリ秒waitした後、チェックを行うようにしています。

まとめ

ざっと使ってみた感想ですが、テストプロジェクトがAndroidプロジェクトではなく通常のJavaプロジェクトということもあり、Androidのオブジェクトは一切触れないので、アプリ内部の状態を検証するようなテストは出来ないようです。
この点に関して言うとRobotiumの方がやりたいようなテストが出来そうな気がします。
とはいえ、iPhoneにも対応していたりGoogle製ということもあり、今後改良が進んで出来ることが増えてくると思うので、注目していきたいなと思います。
というかもっとちゃんと使い方を覚えて、追ってエントリーを書かせていただきたいと思います。