読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

今から5分で,開発中のAndroidアプリを単体テストしよう (JUnitで自動テストする方法)

Android テスト Eclipse JUnit n分


開発中のAndroidアプリを,単体テストフレームワークJUnitを使ってテストする。


Eclipse上で,サンプルアプリを作り始めてから,

アクティビティ内のロジックやUI操作をテストする所までを5分で行なう。

早ければ3分。


記事の末尾には,Androidアプリの自動テストに役立つ情報を集約したリンク集を掲載。


なお,エミュレータを立ち上げていると重くて5分で終わらないので,アプリの動作確認は実機で行なうこととする。


以下から開始。

手順


ここでは新規PJ作成時にテストPJを同時生成するのではなく,

既存PJに対して,テストPJを後付けで新規作成する。



まず,テスト対象のプロジェクトを作成する。


ファイル>新規>Android Application Project で,

「HelloJUnit」という名称で新規PJを作成。

Create Activityはチェック付のまま。



サンプルアプリの仕様を考える。

入力ボックスが2つと,ボタンが1つある。

ボタン押下時には,一方の入力文字列が「Hello, 〜!」と加工されて,もう一方に出力される。



画面のレイアウトとして,下記をコピペ。

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <EditText
        android:id="@+id/et1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />

    <Button
        android:id="@+id/btn1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="実行"
    />

    <EditText
        android:id="@+id/et2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />


</LinearLayout>


アクティビティのコードとして,下記をコピペ。

MainActivity.java

package com.example.hellojunit;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.app.Activity;

public class MainActivity extends Activity {

    private EditText et1;
    private EditText et2;
    private Button btn1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et1 = (EditText)findViewById(R.id.et1);
        et2 = (EditText)findViewById(R.id.et2);
        btn1 = (Button)findViewById(R.id.btn1);

        // 押下時
        btn1.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                // 取得
                String s = et1.getText().toString();

                // 加工
                s = addHello(s);

                // 表示
                et2.setText(s);
            }
       });
    }


    // 加工
    public String addHello(String s) {
        return "Hello, " + s + "!";
    }
}

次に,テスト実行用のプロジェクトを作成する。

新規>その他>Android>Androidテスト・プロジェクト

で,次へ。

PJ名として,HelloJUnitTestを入力。


「Select Test Target」のダイアログで,

テスト対象のPJとして「HelloJUnit」を選択。

完了。



このプロジェクトのAndroidManifest.xmlを開いてみると,

instrumentationというエレメントがある。

この要素は,テスト対象のアプリをモニタする役目を持つ。

要素のtargetPackage属性には,テスト対象のPJのパッケージ名(com.example.hellojunit)がセットされている。

instrumentation
https://sites.google.com/a/techdoctra...

  • アプリケーションのシステムとの相互作用をモニタすることを可能とする Instrumentation クラスを宣言します。
  • android:targetPackage : Instrumentation オブジェクトを実行するアプリケーションです。アプリケーションは、マニフェストファイルで 要素により割り当てられたパッケージ名により識別されます。


また,srcフォルダ内に

  • com.example.hellojunit.test

というパッケージが追加されている。

このフォルダの中にテストケースを追加してゆく。


testフォルダを右クリックし,新規>クラス>適当な名前のクラスを作る。

ここではTestMainという名称にした。



まずは,画面に関係の無いロジックだけをテストしてみよう。

ソースコードとして下記をコピペ。

package com.example.hellojunit.test;

import com.example.hellojunit.MainActivity;

import android.test.ActivityInstrumentationTestCase2;

/**
 * hellojunitプロジェクトのテスト。
 *
 */
public class TestMain extends ActivityInstrumentationTestCase2<MainActivity> {

    private MainActivity mActivity;


    // 必須のコンストラクタ
    public TestMain() {
        super(MainActivity.class);
    }


    @Override
    protected void setUp() throws Exception {
        super.setUp();

        // アクティビティを取得
        mActivity = getActivity();
    }


    public void testAddHelloLogic() {
        assertEquals(mActivity.addHello("山田"), "Hello, 山田!");
    }

}

これで,プロジェクトを右クリック>実行>Android JUnit Test

で,実機を選択して実行する。


Logcatの表示:

Launching instrumentation android.test.InstrumentationTestRunner on 〜〜
Sending test information to Eclipse
Test run finished


Eclipse上では「JUnit」のタブが出現し,実行結果が出る。

  • 実行:1/1 エラー:0 失敗:0

全テストを通過したので,緑色のバーが表示される。

Android SDKが提供するTestCase
http://www.atmarkit.co.jp/fsmart/arti...

  • Instrumentationは、Androidのシステムをフック/制御するメソッドの集まり。アクティビティにキーイベントも送信できる
  • ActivityInstrumentationTestCase2は,モックを利用しない機能テスト(システムテスト)での使用を想定。テスト対象のアクティビティのライフサイクルが実行される
  • ActivityUnitTestCaseは、モックのContextを注入して独立した環境下でテストを実行できる。しかしActivityのライフサイクルは自動的に呼び出されない。


Androidのテストプロジェクト – JUnit test
http://www.netplan.co.jp/archives/1967

  • パッケージ名を指定するコンストラクタはdeprecated(使用すべきではありません,と警告される)


ActivityInstrumentationTestCase2
http://wikiwiki.jp/android/?ActivityI...

  • クラス名の末尾に「2」がつく理由:ActivityInstrumentationTestCaseというクラスも存在しますが、非推奨となっている

では次は,UI操作も含めてテストを書き直してみよう。

package com.example.hellojunit.test;

import com.example.hellojunit.MainActivity;
import com.example.hellojunit.R;

import android.test.ActivityInstrumentationTestCase2;
import android.widget.Button;
import android.widget.EditText;

/**
 * hellojunitプロジェクトのテスト。
 *
 */
public class TestMain extends ActivityInstrumentationTestCase2<MainActivity> {

    private MainActivity mActivity;
    private EditText et1;
    private EditText et2;
    private Button btn1;


    // 必須のコンストラクタ
    public TestMain() {
        super(MainActivity.class);
    }


    @Override
    protected void setUp() throws Exception {
        super.setUp();

        // アクティビティを取得
        mActivity = getActivity();

        et1 = (EditText)mActivity.findViewById(R.id.et1);
        et2 = (EditText)mActivity.findViewById(R.id.et2);
        btn1 = (Button)mActivity.findViewById(R.id.btn1);

    }


    public void testAddHelloLogic() {
        assertEquals(mActivity.addHello("山田"), "Hello, 山田!");
    }


    public void testUI() {
        // 最初は空
        assertEquals(et1.getText().toString().length(), 0);
        assertEquals(et2.getText().toString().length(), 0);

        assertTrue(btn1.isEnabled());


        // UIスレッド上で画面操作
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                et1.setText("World");

                btn1.performClick();

                assertEquals(et2.getText().toString(), "Hello, World!");
            }
        });
        // UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();

    }

}


JUnitタブ上で「テストの再実行」というボタンを押下。


テストを実行すると,実機上では,一瞬だけ画面が表示される。

テストにあわせて素早く,画面が自動操作されているのだ。


実行結果が「実行2/2」で,グリーンバーが表示されればOK。


ロジックもUIも,JUnitからテストを実行することができた。



※ここで,画面部品であるビューの操作は,すべてrunOnUiThreadでUIスレッド上で行っている。

もしそれを守らないと,下記のようなエラーになる。

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRoot.checkThread(ViewRoot.java:2961)
at android.view.ViewRoot.invalidateChild(ViewRoot.java:646)
at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:672)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
at android.view.View.invalidate(View.java:5268)
at android.widget.TextView.invalidateCursor(TextView.java:3797)
at android.widget.TextView.spanChange(TextView.java:6660)
at android.widget.TextView$ChangeWatcher.onSpanAdded(TextView.java:6785)
at android.text.SpannableStringBuilder.sendSpanAdded(SpannableStringBuilder.java:906)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:611)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:514)
at android.text.Selection.setSelection(Selection.java:74)
at android.text.Selection.setSelection(Selection.java:85)
at android.text.method.ArrowKeyMovementMethod.initialize(ArrowKeyMovementMethod.java:268)
at android.widget.TextView.setText(TextView.java:2816)
at android.widget.TextView.setText(TextView.java:2652)
at android.widget.EditText.setText(EditText.java:81)
at android.widget.TextView.setText(TextView.java:2627)
at com.example.hellojunit.test.TestMain.testUI(TestMain.java:56)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:204)
at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:194)
at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:186)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:529)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1448)

役立つリンク集


Androidアプリ開発に特有のテスト事情(と,その解決策):

Android開発で泣かないための「テスト」の重要性 / 第1回Androidテスト祭りレポート(2011年9月)
http://www.atmarkit.co.jp/fsmart/arti...

  • 端末ごとに性能差。メーカーの独自カスタマイズで有名アプリが動作しないことも。キャリアとメーカーとOSバージョンの膨大な組み合わせ
  • 試験コストを掛けて大ヒットを狙うか。 or 互換性を低くして,超高速開発でニッチ市場に対して小ヒットを狙うか。
  • 組み込み機器では割り込みに起因する不具合が多く,Androidも同じ。ある状態から別の状態に遷移中に通話やメール、アラームなど何らかの割り込みが入っても不具合が生じなければ,きちんと実装されているという判断も。


トップAndroidデベロッパーはこうしてアプリの品質テストを行っている(2012年6月)
http://jp.techcrunch.com/archives/201...

  • Googleが公表するシェアデータを見ながら,2:8の法則(80/20ルール)に従って,ユーザーの大半をカバーする少数の機種を識別。
  • 自社アプリの利用ユーザ層のデバイス分布を調べて,さらに対象機種を調整する。
  • 弱い機種や古いOSにノーと言うことも大切

一度は読み通すべき,良質なまとめやリファレンス:

o. テスト - ソフトウェア技術ドキュメントを勝手に翻訳
http://www.techdoctranslator.com/andr...


連載インデックス「Androidアプリ開発テスト入門」 - @IT(2011〜2012年)
http://www.atmarkit.co.jp/fsmart/inde...

  • 基礎知識とテスト自動化から,モックやCI,システムテスト・受け入れテストまで


ニュース - Androidアプリのテストガイドライン、OESFが公開:ITpro(2012年11月)
http://itpro.nikkeibp.co.jp/article/N...

  • http://openlab.oesf.biz/modules/projects/index.php?content_id=4
    • テスト対象コンポーネントの分類:ビジネスロジック,UI,通信,デバイス
    • テスト作業の3軸を定義:Level(テスト工程の段階)/Type(担保したい品質特性)/Style(実施手段)
    • 端末の多様性に対するソリューション:多数の端末を特性で分類。テスト自動化により反復作業を省力化。CIやテストツールの導入。

単体テストのチュートリアル集:

AndroidアプリケーションをJUnitでテストする | Android開発メモ
http://itinfo.main.jp/tan/?p=35

  • プロジェクト作成時に,テストプロジェクトを同時生成する方法
  • ActivityInstrumentationTestCase2を使用


JUnitを使ってAndroidの自動テスト(1) - スマートフォンアプリ開発会社のエンジニアブログ
http://d.hatena.ne.jp/glpgsinc/201105...


AndroidでのUnitTestの始め方 | ひたすらメモするだけのブログ
http://www.yaunix.com/2011/01/08/andr...

  • 既存のプロジェクトに,テストプロジェクトを後から新規作成する方法
  • ActivityUnitTestCaseを使用


JUnitについて:

Android 向け 単体テスト UnitTest(JUnit) を作成する
http://uguisu.skr.jp/Windows/android_...

  • JUnitで用意されているメソッドの一覧表


JUnitチュートリアル
http://www.次世代創造機構.jp/android/android...

  • JUnitそのものの解説・入門と,Androidアプリでの実行方法


技術 / Android / TestAutomation
http://www.glamenv-septzen.net/view/987

  • JUnit + Instrumentationは,Android上でのActivityTestの基本。
  • instrumentは,仮想マシンとクラスの間に入り込んでインターセプトする。android.testパッケージ以下にクラスが存在。
  • 各種Test実行時に,内部的には,UIを操作「したことに」している。

他のテスト方法やツール:

ASCII.jp:NTTレゾナント、Androidの実機テストをクラウド化
http://ascii.jp/elem/000/000/743/743044/

  • データセンターにある「実機」の画面をパソコンに転送し、インターネット経由で、パソコンから実機を遠隔操作
  • Androidアプリをさまざまな機種で動作テストする費用は,アプリ開発にかかる全コストの55%にあたるケースもある。
  • 2012年の情報


GroovyなAndroidテスト #atest_hack
http://www.slideshare.net/alterakey/g...

  • Groovy(JVM上の動的言語)とRobolectric+Spockを使ってAndroidアプリをBDD (振舞駆動開発)する方法のスライド。
  • 2012年9月の情報


Androidアプリの自動テストツールで最も有望か - 「NativeDriver」,Google製「WebDriver」の拡張 (公式のAndroid版Selenium)
http://language-and-engineering.hatenablog.jp/entry/20110930/p1

  • 2011年9月の情報
  • UIテストに特化。ビジネスロジックなどの内部レイヤ向けではない。


Taosoftware: AndroidのテストツールMonkey
http://www.taosoftware.co.jp/blog/200...

  • adbを経由してモンキーテストを実施する方法。2009年の情報。


関連する記事:

Androidアプリの自動テストツールで最も有望か - 「NativeDriver」,Google製「WebDriver」の拡張 (公式のAndroid版Selenium)
http://language-and-engineering.hatenablog.jp/entry/20110930/p1


Androidでライブラリ・プロジェクトを作成し,Eclipse上でコードを共有しよう
http://language-and-engineering.hatenablog.jp/entry/20130116/AndroidLibraryPr...


Ruby on Railsのテストの書き方 (モデルの単体テストと,コントローラの機能テスト)
http://language-and-engineering.hatenablog.jp/entry/20091023/p1