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