Android テストフレームワーク NativeDriver
こんにちは~うえじゅんです。
今回から何回かに分けてAndroidのテストフレームワークを試してみようと思います。
まずは比較的最近Googleが発表した"NativeDriver"を使ってみます。
環境構築についての詳細は以下のドキュメントを読んでください。
"server-standalone.jar"と"client-standalone.jar"を作成します。
svn checkout https://nativedriver.googlecode.com/svn/trunk nativedriver --username {Google account e-mail address}
{}内は各自のGmailアドレスを入れてください。
SVNから取得ができたらチェックアウトとしてきたフォルダに移動してください。
cd nativedriver/android
最後に"ant"コマンドを実行すると各種jarファイルが生成されます。
ant
※ antコマンドが実行できない方は、以下からダウンロードしてください。
ということで、サンプルプロジェクトの作成へ進んでいきましょう。
サンプルプロジェクトの作成
ではまずはNativedriverSampleという名前でAndroidプロジェクトを作成します。
環境構築で作成した"server-standalone.jar"を追加しましょう。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cm.blog.sample" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".NativedriverSampleActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <instrumentation android:targetPackage="cm.blog.sample" 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" /> </manifest>
AndroidManifest.xml に instrumentation と uses-permission を追加します。
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Spinner android:id="@+id/spinner" android:layout_width="fill_parent" android:layout_height="wrap_content"> </Spinner> <EditText android:id="@+id/editText" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/button" android:text="@string/button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClick"/> </LinearLayout>
main.xml には Spinner、EditText、Button を配置します。
strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">NativedriverSample</string> <string name="button">クリア</string> </resources>
strings.xml にはボタン名を追加します。
後はActivityを作成するだけですね。
NativedriverSampleActivity.java
package cm.blog.sample; import java.util.ArrayList; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; public class NativedriverSampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Listの作成 ArrayList<String> list = new ArrayList<String>(); // Listにデータを入れる list.add("クラスメソッド開発ブログ"); list.add("クラスメソッドデザインブログ"); list.add("クラスメソッドセールスブログ"); list.add("クラスメソッドバックオフィスブログ"); list.add("クラスメソッド経営ブログ"); // Adapterの作成 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list); // ドロップダウンのレイアウトを指定 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // SpinnerにAdapterを関連付ける Spinner spinner = (Spinner) findViewById(R.id.spinner); spinner.setAdapter(adapter); // Spinnerのアイテムが選択された時に呼び出されるリスナーを登録 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Spinner spinner = (Spinner) parent; // 選択されたアイテムを取得 String item = (String) spinner.getSelectedItem(); // EditTextに選択されたアイテムをセット EditText editText = (EditText) findViewById(R.id.editText); editText.setText(item); } @Override public void onNothingSelected(AdapterView<?> arg0) { } }); } public void onClick(View v) { // EditTextの値をクリア EditText editText = (EditText) findViewById(R.id.editText); editText.setText(""); } }
これでテスト対象のアプリは完成です。
テストプロジェクトの作成
では Nativedriver でテストするためのプロジェクトを作成しましょう。
NativedriverSampleTestというJavaプロジェクトを作成します。
ライブラリーにはJUnit 3を追加してください。
こちらでも環境構築で作成した"client-standalone.jar"を追加しましょう。
準備が整ったらテストクラスを作成します。
NativedriverSampleTest.java
package cm.blog.sample; 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 class NativedriverSampleTest extends TestCase { private AndroidNativeDriver driver; @Override protected void setUp() throws Exception { driver = getDriver(); } @Override protected void tearDown() throws Exception { // アプリを終了 driver.quit(); } protected AndroidNativeDriver getDriver() { return new AndroidNativeDriverBuilder().withDefaultServer().build(); } private void startActivity() { // Activityを登録 driver.startActivity("cm.blog.sample." + "NativedriverSampleActivity"); } public void testSpinnerのアイテムを選択() { startActivity(); // Spinnerを取得 AndroidNativeElement spinner = driver.findElement(By.id("spinner")); spinner.click(); // Spinnerの要素をクリック driver.findElement(AndroidNativeBy.text("クラスメソッドセールスブログ")).click(); // EditTextを取得 AndroidNativeElement editText = driver.findElement(By.id("editText")); assertEquals("クラスメソッドセールスブログ", editText.getText()); } public void testEditTextの値を変更() { startActivity(); // Buttonを取得 AndroidNativeElement button = driver.findElement(By.id("button")); button.click(); // EditTextを取得 AndroidNativeElement editText = driver.findElement(By.id("editText")); assertEquals("", editText.getText()); // 値を入力 editText.sendKeys("Classmethod.dev()"); assertEquals("Classmethod.dev()", editText.getText()); } }
ポイントとしては、"startActivity()"でテスト対象のActivityをセットするのですが、
パッケージ + Activityという形にしてください。
SpinnerやEditText、Button等のコンポーネントを取得する際には
driver.findElementメソッドを利用します。
引数には対象のIDを指定してください。
AndroidNativeElement spinner = driver.findElement(By.id("spinner"));
上記の例では Spinner コンポーネントを取得することができます。
テストの実行
では、実際にテストを実行してみましょう。
サンプルアプリである NativedriverSample を端末にインストールしてください。
その後コマンドプロンプトから以下のコマンドを実行してください。
adb shell am instrument cm.blog.sample/com.google.android.testing.nativedriver.server.ServerInstrumentation
cm.blog.sample の部分が各自が作成したアプリのパッケージを指定する形になります。
エラーが出てしまった場合はパッケージと比べてみてください。
正常に実行されれば、次は以下のコマンドを実行します。
adb forward tcp:54129 tcp:54129
ここまでくれば後は実行するだけです。
NativedriverSampleTest プロジェクトを右クリックして JUnit テストを実行してみましょう。
上記のような動作が確認できましたでしょうか。
まとめ
NativeDriver はテスト自動化のフレームワークとしてはなかなか面白いですがまだまだできることが少ない感じです・・・
ドキュメントを見ていてもまだ実装されていないものが多いです。
Googleが主導で進めているので今後に期待ですね。
実際に試してみての注意点や不明点は以下になります。
解決策を知っている方がいれば教えてください。
- エミュレーターではまともに動かなかった。
→ Spinnerのダイアログが上がるまで待つ等の方法はあるか。
標準のテストフレームワークで言うと getInstrumentation().waitForIdleSync() のようなもの。
ご存知の方がいらっしゃいましたら、ぜひ教えてください。
- パッケージ系のパスを記述する部分がミスっているとコマンドが通らなくなるのではまりやすく、原因の特定をしにくい。
→ 純粋に指定ミスではあるが、パッケージを記載する部分が多数あるので、気がつかないとはまりやすい。
- キーボードのモードが英語でないと今回のテストは失敗してしまう。
→ キーボードのモードを変えることはできるのか。
最近 iPhone でもテストができるようになったようです。
テストフレームワークとしては、端末のOSに縛られずにテストを実装できるので、魅力的ですね。
あとはもう少しできることが増えればこれで決まりと言える日が来るかもしれませんね。