-
Notifications
You must be signed in to change notification settings - Fork 351
2.11. テスト
この章では、ユニットテストとシナリオテストについて解説します。
参考:Testing Fundamentals | Android Developers
参考:Activity Testing | Android Developers
参考:Service Testing | Android Developers
参考:Content Provider Testing | Android Developers
参考:UI Testing | Android Developers
参考:What To Test | Android Developers
Android では、アプリ本体のプロジェクトと、テストケース用のプロジェクトが分離されています。
テストプロジェクトのセットアップは、Eclipse 上から実行することができます。
File > New から、Project... または Other... を選択してください(Ctrl + N または Command + N)。
新規プロジェクトの画面から、Android > Android Test Project を選択します。
Launch Test Project Wizard
次に、テストプロジェクトのセットアップをします。
テストプロジェクトの名前と、配置場所を指定します。
Test Project Configuration
次に、テストのターゲットとするアプリ本体のプロジェクトを選択します。
Select target
最後に、テストプロジェクトが利用する SDK のバージョンを選択して完了です。
Select SDK Version
このテストプロジェクトを用いて、ユニットテストや、シナリオテストを記述していきます。
Android では、フレームワーク自身がユニットテストのフレームワークを持っています。
このフレームワークは、JUnit3 をベースにしています。
テスト対象に応じて、独自に拡張されたテストケースクラスが用意されています。
一般的な、Android のテストケースクラスです。
Context、特にActivityのContextを必要とするクラスのテストをするために作られています。
これによって、リソースへのアクセスも可能となっています。
テストケースは、メソッドごとに定義します。
この時、メソッド名の命名規則として、先頭に必ずtestと記述する必要があります(JUnit3 の仕様)。
また、テストケースは、記述した順に実行されるとは限りません。
テストケースを実行するごとに、前準備と後始末をするためのメソッドも用意されています。
それぞれ、事前準備をするメソッドはAndroidTestCase#setUp()、後始末をするメソッドはAndroidTestCase#tearDown()です。
特に、テスト内でファイル I/O やデータベース接続などのリソースを扱う場合は、後始末をするtearDown()で、必ずリソースのクローズを行うようにします。
以下に、テストのターゲットとするクラスのサンプルと、そのターゲットをテストするサンプルを示します。
public class SampleTestTarget1 { public int add(int left, int right) { return left + right; } public int div(int left, int right) { if (right == 0) throw new IllegalArgumentException("right operand must not be zero."); return left / right; } }
public class MyTestCase extends AndroidTestCase { // テストの前準備のメソッド。テストケースの実行ごとに呼ばれる。 // 事前準備中に何らかの例外が起こる可能性があるので、例外をスローする宣言をする。 @Override protected void setUp() throws Exception { super.setUp(); } // テストの後始末のメソッド。テストケースの実行ごとに呼ばれる。 // 後始末中に何らかの例外が起こる可能性があるので、例外をスローする宣言をする。 @Override protected void tearDown() throws Exception { super.tearDown(); } // テストケース本体。名前は必ず test から始まる // テスト中に異常が発生した場合(=例外がスローされた場合)、テストを Fail とするためその例外をそのまま投げるようにする public void testAdd() throws Exception { SampleTestTarget1 target = new SampleTestTarget1(); // 第 1 引数 に期待値、第 2 引数に実際の計算を入れて、等しいかどうか比較する assertEquals(2, target.add(1, 1)); assertEquals(3, target.add(1, 2)); assertEquals(4, target.add(2, 2)); } public void testDivide() throws Exception { SampleTestTarget1 target = new SampleTestTarget1(); assertEquals(2, target.div(4, 2)); try { target.div(1, 0); // 期待した例外が来ない場合、強制的に Fail する fail("no argument checking!?"); } catch (IllegalArgumentException e) { // 例外をテストする場合。 // テストしたい例外のみをキャッチして、それ以外は throws 宣言で Fail 扱いとする // テストしたい例外をキャッチした上で、何もしなければ Pass 扱いとなる } } }
テストで利用する、検証用のメソッドは以下のようなものがあります。
引数を 2 つとるメソッドは、第 1 引数が期待する値、第 2 引数が実際にテスト対象を呼び出した結果を渡すようにします。
| メソッド | 用途 |
|---|---|
assertEquals() |
第 1 引数と第 2 引数が等しいかどうか |
assertNull() |
引数のオブジェクトがnullかどうか |
assertNotNull() |
引数のオブジェクトがnullでないかどうか |
assertTrue() |
引数のbooleanがtrueかどうか |
assertFalse() |
引数のbooleanがfalseかどうか |
assertSame() |
第 1 引数と第 2 引数が、同じオブジェクトを参照しているかどうか |
assertNotSame() |
第 1 引数と第 2 引数が、同じオブジェクトを参照していないかどうか |
これらは、JUnit3 由来の検証メソッドですが、これら以外にも検証の手段が提供されています。
MoreAsserts | AndroidDevelopers
ViewAsserts | AndroidDevelopers
ContentProviderのためのテストケースです。
テスト用に、モックのContextを利用してテストを行います。
テストを実行すると、テスト用に本体とは分離されたContextのもと、データベース等もすべてテスト向けに特別なものが用意されます。
このため、本体で利用するデータベースへの影響はありません。
ContentProviderのテストでは、queryやinsert、update、delete、getType、onCreateの動作を保証しておくことが必要です。また、不正なパラメータが与えられた場合などの異常系のテストも必要です。
加えて、ContentProviderを外部向けにエクスポートする場合、アクセスに利用するUriについての正常系と異常系のテストを含めておくことが求められます。
// ProviderTestCase ではなく ProviderTestCase2 を継承すること public class SampleContentProviderTestCase extends ProviderTestCase2<TestTargetContentProvider> { private Context mMockContext; public SampleContentProviderTestCase() { this(TestTargetContentProvider.class, TestTargetContentProvider.AUTHORITY); } public SampleContentProviderTestCase(Class<TestTargetContentProvider> providerClass, String providerAuthority) { super(providerClass, providerAuthority); } @Override protected void setUp() throws Exception { super.setUp(); mMockContext = getMockContext(); } public void testInsertNewText() throws Exception { // モックされたコンテキストから、ContentResolver を取り出し、それを経由してデータベースへとアクセスする // テストからアクセスする場合、毎回データベースが作りなおされるため、後始末としてテストで利用したデータを消すなどは必要ない // また、データベースそのものも、テスト用のものが作成されるため、本体のデータベースには影響を及ぼさないようになっている ContentResolver resolver = mMockContext.getContentResolver(); ContentValues values = new ContentValues(); values.put("name", "KeithYokoma"); Uri newUri = resolver.insert(TestTargetContentProvider.CONTENT_URI, values); assertNotNull(newUri); assertEquals(ContentUris.withAppendedId(TestTargetContentProvider.CONTENT_URI, 1), newUri); } }
Serviceのためのテストケースです。
Serviceが期待通りのライフサイクルで動作することを保証するためのテストを記述します。
public class SampleServiceTestCase extends ServiceTestCase<TestTargetService> { public SampleServiceTestCase() { this(TestTargetService.class); } public SampleServiceTestCase(Class<TestTargetService> serviceClass) { super(serviceClass); } public void testStartingProperly() throws Exception { // サービスを問題なく開始できること // 何かあれば例外が飛ぶこともチェック startService(new Intent(getContext(), TestTargetService.class)); } public void testBinding() throws Exception { // 開始するサービスの場合で、バインドをサポートしない場合は、bindService の返り値が null となるので、それをチェック IBinder binder = bindService(new Intent(getContext(), TestTargetService.class)); assertNull(binder); } }
Contextをモックするための仕組みとして、MockContextクラスが用意されています。
このクラス自身は、どのメソッドを読んでも、UnsupportedOperationExceptionを投げるだけになっており、自分でモックしたい部分や、モックすべき部分を、MockContextを継承して記述し、各テストケースクラスで、そのContextを注入して使います。
public class SampleTestTarget2 { public void startSubActivity(Context context, String hogeExtra) { Intent intent = new Intent(context, SubActivity.class); intent.putExtra("hoge", hogeExtra); context.startActivity(intent); } }
public class SampleTestTarget2TestCase extends AndroidTestCase { @Override protected void setUp() throws Exception { super.setUp(); } @Override protected void tearDown() throws Exception { super.tearDown(); } // startSubActivity() を呼び出すテスト public void testStartSubActivity() throws Exception { SampleTestTarget2 target = new SampleTestTarget2(); // AndroidTestCase が持っている ActivityContext ではなく、自分でモックした Context target.startSubActivity(new SampleTestTarget2Context(getContext()), "hogehoge"); } // Context#startActivity() が、期待通りのコンポーネントに Intent を投げているかテストするための // MockContext private static class SampleTestTarget2Context extends MockContext { private Context mContext; public SampleTestTarget2Context(Context baseContext) { mContext = baseContext; } @Override public String getPackageName() { return mContext.getPackageName(); } @Override public void startActivity(Intent intent) { // Intent から、Intent の送り先のコンポーネント情報を取り出して、期待値と一致するか確認する ComponentName component = intent.getComponent(); assertEquals(SubActivity.class.getCanonicalName(), component.getClassName()); // Extra に期待するものが有るか確認する assertTrue(intent.hasExtra("hoge")); assertEquals("hogehoge", intent.getStringExtra("hoge")); } } }
実行したいテストクラスを選択して、テストを実行します。
右クリックメニューから、Run As > Android JUnit Test を選択すると、自動でターゲットプロジェクトとテストプロジェクトをそれぞれビルドし、ターゲットプロジェクトの apk とテストプロジェクトの apk をインストールし、テストが動き始めます。
テストが動き始めると、順次テスト結果が表示されます。
JUnit Test Result
すべて成功すると、緑色のインディケータが表示されます。
なにか 1 つでも失敗したケースがあると、その時点でインディケータの色が紺色になります。
また、実行したテストケースの一覧も表示されます。
この一覧のなかで、成功・失敗の表示がありますので、どのテストケースが成功・失敗したかが分かるようになっています。
ユニットテストでは、各クラス(特にモデルのクラス)が適切な振る舞いをするかどうかをテストしました。
シナリオテストでは、ボタンを押したり、画面を回転した時に、アプリがどのように振る舞うかをテストします。
これを機能テストとも呼んだりしています。
Activity に対して、テストプロジェクトから様々な操作を行うためのテストケースクラスです。
ActivityInstrumentationTestCase だけでは足りない、より幅広い UI のテストをするためのフレームワークです。
ADT リビジョン 21 以降から導入され、Android 4.1 以降で動作するように作られた、UI のテストフレームワークです。
Android SDK に含まれています。