JUnitを使ってAndroidの自動テスト(2)

こんにちは。Team A(ndroid)の@knqyf263です。
前回JUnitの導入について書いたので今回は具体的にどんなテストコードが書けるのか、について詳しく書いていきます。
特にUI操作を自動化できると便利だと思うのでそこら辺を中心に書きます。
例によって嘘を書くかもしれませんので、参考にする場合は自己責任でお願いします。
あとやたら基本的なことも書くのでさくさく読み飛ばしてくださって結構です。

第1回 JUnitを使ってAndroidの自動テスト(1) - スマートフォンアプリ開発会社のエンジニアブログ

Sampleアプリ作成

今回も簡単なアプリのテストを行ってみたいと思います。
http://ascii.jp/elem/000/000/539/539591/
にわかりやすいアプリ(sample04.zip)があったのでこれを改変して使わせていただきます。
仕様としては

・TextView×2
・EditText×1
・Button×1
・一つ目のTextViewの初期値は「GALAPAGOS
・二つ目のTextViewの初期値は「NAME」
・EditTextの初期値は「Enter NAME」
・Buttonのラベルは「Set」
・EditTextに名前を入れ、Setボタンを押すと、上のTextViewに表示される。
・EditTextにフォーカスした状態で、端末の右ボタンを押すとSetボタンにフォーカス
という形になっている。写真は以下。

今回はアプリ作成が目的ではないので、作る過程は多少省略します。
下にファイルを置いておいたので、それをダウンロードしてそれをインポートします。
Sample01.zip 直

まずダウンロードしたSample01.zipを解凍します。
その後、Eclipseで、「ファイル」→「インポート」→「一般」→「既存プロジェクトをワークスペースへ」(下図参照)
  

としたあと、「ルート・ディレクトリの選択」をチェックし、右の「参照」を押して、自分がSample01を解凍したフォルダを指定し、「完了」をクリック

これでワークスペースにインポートされると思います。



テスト用プロジェクト「Sample01Test」の作成

まず、パッケージ・エクスプローラーのSample01の上で右クリック→「新規」→「その他」をクリック

その後の画面で、「Android」→「Android Test Project」と進み、「次へ」をクリック



・Test Project Name:Sample01Test

と入力し、
An existing Android projectの参照ボタンを押しSample01を選択した後、「完了」をクリック。



Sample01Testの編集

すると、Sample01Testが作成されていると思うので、
「Sample01Test」→「src」→「com.glpgs.sample01.test」
と進み、「com.glpgs.sample01.test」の上で右クリック。
前回と同様に、「新規」→「クラス」と進み、

名前:TestCase
スーパークラス:ActivityInstrumentationTestCase2

を入力し「完了」。
これでTestCase.javaが作成されていると思います。

テストコードの作成

ようやくこれから本日の本題のテストコード作成です。
UI操作について触れていきます。
前回と同様にエラーを修正し、コンストラクタを追加。

public TestCase() {
        super("com.glpgs.sample01", MainActivity.class);
}

例によって
AndroidアプリケーションをJUnitでテストする | Android開発メモ
を相当に参考にさせていただいております。
まず、オブジェクトとリソースを結び付けます。それは以下のコード。

//アクティビティを取得する
mActivity = getActivity();
//各オブジェクトとリソースを結び付ける
mEditTextFrom = (EditText) mActivity.findViewById(R.id.InputText);
mTextViewTo = (TextView) mActivity.findViewById(R.id.Message);
mButtonSet = (Button) mActivity.findViewById(R.id.SetButton);

この処理はsetUp()に書くといいと思われます。setUp()はテストメソッドの前に必ず呼び出されます。
さて、肝心のUI処理に移ります。
まず大前提として重要なことは
・UI操作はUIスレッドで行わなければならない
ということです。
UIスレッドは、runOnUiThreadを用いて作成します。
そしてもう一つ重要なのは、
・waitForIdleSyncを用いてUIスレッド処理の終了を待つ必要がある
ということです。
コードを示すと以下です。

getActivity().runOnUiThread(new Runnable() {
    @Override
    public void run() {
        
    }
});
getInstrumentation().waitForIdleSync();

上のrun(){}の中にUI操作を記述します。
具体的にどのようなことができるのか、ですが、例をいくつかあげます。
・フォーカスの移動
・キーイベントの発生
・ボタンのタップ
などなどです。とりあえず今回は上の三つを試してみたいと思います。

フォーカス位置の確認

まず、EditTextにフォーカスした状態で、右ボタンを押したらちゃんとSetボタンにフォーカスするかどうかのテストを行います。
testGoingRightFromEditText()というメソッドを作り、その中でEditTextにフォーカスします。
オブジェクトに対してフォーカスを移すには、各オブジェクトに対してrequestFocus()を呼ぶ必要があります。

そして、キー入力の方法ですが、sendKeysメソッドを呼び、引数にKeyEventを与えるとキーイベントが発生します。
aを入力させたければ

sendKeys(KeyEvent.KEYCODE_A);

のように記述します。
今回は右ボタン押下のイベントを発生させたいので、

sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);

のように書きます。
そしてちゃんとフォーカスされているかの確認は、mButtonSet.isFocused()がTRUEかを見ればよいので、

assertTrue("set button should be focused", mButtonSet.isFocused());

で確認できます。
以上から、完成版メソッドは以下の通りです。

public void testGoingRightFromEditText() {
        //EditTextにフォーカスを移す
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                mEditTextFrom.requestFocus();
            }
        });
        //UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();
        int length = mEditTextFrom.getText().toString().length();
        
        for(int i = 0; i < length; i++){
        	sendKeys(KeyEvent.KEYCODE_DEL);
        }
        
        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
        //UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();
        assertTrue("set button should be focused", mButtonSet.isFocused());
}

sendKeys(KeyEvent.KEYCODE_DEL);のくだりは単にEditTextの文字を消すためのものなのであまり気にしなくていいです。
BACK SPACEもできるんだなぐらいに考えてください。

キー入力とボタンのタップ

それではEditTextにキー入力をして、Setボタンを押し、その内容がTextViewに反映しているかの確認を行いたいと思います。
ボタンタップのイベントは、ボタンオブジェクトに対して、performClick()メソッドを呼び出すことで、イベントを発生させることができます。

キー入力までの流れはさっきのメソッドと同じです。
EditTextにフォーカス→キーイベントの発生
その後、
Buttonにフォーカス→performClick() の流れを追加するだけなので簡単です。
最後に、EditTextの内容がTextViewにコピーされているかを確認して終了です。
特に新しいことはないので解説は省略します。
コードを示しておきます。

 public void testInputEditText() {
        //EditTextにフォーカスを移す
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                mEditTextFrom.requestFocus();
            }
        });
        //UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();
        int length = mEditTextFrom.getText().toString().length();
        
        for(int i = 0; i < length; i++){
        	sendKeys(KeyEvent.KEYCODE_DEL);
        }
        //EditTextに「test」を入力する
        sendKeys(KeyEvent.KEYCODE_T);
        sendKeys(KeyEvent.KEYCODE_E);
        sendKeys(KeyEvent.KEYCODE_S);
        sendKeys(KeyEvent.KEYCODE_T);
        
        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
        assertTrue("right button should be focused", mButtonSet.isFocused());
      
        //UIスレッドが終了するまで待つ      
        getInstrumentation().waitForIdleSync();
        
        //ボタンにフォーカスを移す
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                mButtonSet.requestFocus();
            }
        });
        //UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();
        
        //ボタンをタップする
        mActivity.runOnUiThread(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                mButtonSet.performClick();
            }
        });
        //UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();
        
        //EditTextの内容がTextViewにコピーされているかを確認する
        assertEquals(mEditTextFrom.getText().toString(), mTextViewTo.getText().toString());
}

とりあえず今回はこんな感じです。途中心が折れてしまって細かく解説するのを省略してしまいましたので、Sample01Testのサンプルプロジェクトを載せておきます。
Sample01Test.zip 直

今回のテストを実際に行うとどうなるのか、という動画を作ったので載せておきます。
上の動画は全体を入れたのですが、見づらかったのでアプリに必要な部分だけを大きくしたのが下の動画です。
Androidエミュレータを使った人は分かると思いますが、文字入力もいちいちもっさりです。
自動化すると早いですし、イライラも解消されていいこと尽くしだなー。