Android テストフレームワーク NativeDriver

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは~うえじゅんです。

今回から何回かに分けてAndroidのテストフレームワークを試してみようと思います。

まずは比較的最近Googleが発表した"NativeDriver"を使ってみます。

環境構築についての詳細は以下のドキュメントを読んでください。

GettingStartedAndroid

"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コマンドが実行できない方は、以下からダウンロードしてください。

Downloading Apache 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に縛られずにテストを実装できるので、魅力的ですね。

あとはもう少しできることが増えればこれで決まりと言える日が来るかもしれませんね。