JUnitとは単体試験をサポートするツール+ライブラリとなっている。何が良いのかというと以下の通り。
実際にJUnitを使って見る。まず、簡単な例で以下のTestTargetクラスのfooメソッドをテストして見よう。
package com.suddenAngerSystem;
import android.app.Activity;
import android.os.Bundle;
public class JUnitModel extends Activity {
}
class TestTarget {
public static int foo() {
return 10;
}
}
JUnitを使う際にはおきまりのコードを書く必要があるが、それはeclipseのプラグインに全て任せてしまいます。また、プロジェクトでテスト用クラスの名前付け規則とテスト単位(通常はクラス単位)を決めておく必要があります。(ここでは先頭にTestを付けたクラスをテスト用にし、テスト単位はクラス単位とします。)
まず、ファイルメニューでJUnitテストケースを選択
ダイアログでテスト対象のクラスを選択し、テストクラス名を入力。setUp、tearDown等は後で説明します。
次にテスト対象のメソッドを選択します。選択したメソッドについて自動的にeclipse側で対応するテストメソッドが作成されます。ここではfooを選択してください。
生成されたソースは以下の様になります。
package com.suddenAngerSystem;
import junit.framework.TestCase;
public class TestTestTarget extends TestCase {
public void testFoo() {
//ここにfooメソッドをテストする処理を記載する。
fail("まだ実装されていません");
}
}
JUnitの実行のためにはjarファイルが必要です。通常のjavaプロジェクトの場合はJUnitのためのjarファイルが自動的に追加されますが、そうでない場合は手動で追加する必要があります。追加するためにはまず、プロジェクトのプロパティを選択します。
次にjavaのビルドパスのライブラリタブを選択し、ライブラリの追加を選びます。
次にJUnitを選択します。
まだ何もテストケースを実装していませんが、自動生成されたソースでJUnitを実行してみます。以下の通りにテストメソッドを選択し、コンテキストメニューでJUnitの実施を選択します。
AndroidプロジェクトではJUnitの機構が二種類選択できますが、ここでは標準のeclipseのものを選択します。実行後には以下の様に結果が表示されます。また、デバッグメニューから選択するとデバッガを使用した実行ができます。
また、プロジェクト全体を選択してJUnitで実行させるとJUnitのテストケース全てが実行されます。(TestCaseクラスを継承しているユーザークラスを検索して、public void テスト名()のシグネチャをもつメソッドを全部起動してくれます)
他にもテスト構造を示すクラスを使う事によって、テスト範囲を規定したりできますが、ここでは説明しません。
Hello Worldではstaticメソッドをテスト対象とした事と、全く中身をテストしませんでした。今度はまともにテストしてみましょう。以下がテスト対象のクラスです。
package com.suddenAngerSystem;
//カスタム整数クラス
public class CustomInteger {
//実体値
private int value_;
//コピーコンストラクタ
public CustomInteger(CustomInteger value) {
value_ = value.value_;
}
//コンストラクタ
public CustomInteger(int value) {
value_ = value;
}
//足し算
public CustomInteger add(CustomInteger rhs) {
value_ += rhs.value_;
return this;
}
//足し算
public CustomInteger add(int rhs) {
value_ += rhs;
return this;
}
//引き算
public CustomInteger subtraction(CustomInteger rhs) {
value_ -= rhs.value_;
return this;
}
//引き算
public CustomInteger subtraction(int rhs) {
value_ -= rhs;
return this;
}
@Override
public boolean equals(Object rhs) {
return value_ == ((CustomInteger)rhs).value_;
}
public int getValue_() {
return value_;
}
public void setValue_(int value_) {
this.value_ = value_;
}
}
TestCaseクラスはチェック用の沢山のstaticメソッドを持っており、ここではassertEqualsを使ってチェック処理をしています。一つ目の引数は失敗時のメッセージ、二つ目、三つ目の引数は同じかどうかを比較するオブジェクトになります。失敗時のメッセージは省略できますが、テストに失敗した時に分かりづらくなるため、記載して置いた方が無難です。また、テスト対象クラスのインスタンス生成等の共通的なテスト実施前処理についてはJUnitがテストメソッド起動前にsetUpメソッドを起動してくれる仕組みになっているため、setUpメソッドで実施しています。そして、テスト対象クラスの後始末等の共通的な処理についてはtearDownメソッドで実施する事が可能です。(この例ではあまり役に立っていませんが…)
package com.suddenAngerSystem;
import junit.framework.TestCase;
public class TestCustomInteger extends TestCase {
private CustomInteger customInteger_;
public TestCustomInteger(String name) {
super(name);
customInteger_ = null;
}
//各テストメソッドが起動される直前に起動される
protected void setUp() throws Exception {
//テスト対象のクラスのインスタンスを生成
customInteger_ = new CustomInteger(0);
}
//各テストメソッドが終了した直後に起動される
protected void tearDown() throws Exception {
customInteger_ = null;
}
public void testCustomIntegerCustomInteger() {
final CustomInteger source = new CustomInteger(10);
final CustomInteger result = new CustomInteger(source);
//結果のチェック
assertNotNull("コピーコンストラクタでなぜかnull", result);
assertEquals("コピーコンストラクタ失敗", source, result);
}
public void testCustomIntegerInt() {
final CustomInteger result = new CustomInteger(10);
//結果のチェック
assertEquals("コンストラクタ失敗", 10, result.getValue_());
}
public void testAddCustomInteger() {
final CustomInteger rhs = new CustomInteger(10);
customInteger_.add(rhs);
//結果が10になっているかチェック(equalsメソッドがオーバーライドされていないクラスインスタンスには使えない。)
assertEquals("加算失敗(CustomInteger)", customInteger_, rhs);
}
public void testAddInt() {
customInteger_.add(10);
//結果が10になっているかチェック(equalsメソッドがオーバーライドされていないクラスインスタンスには使えない。)
assertEquals("加算失敗(int)", customInteger_, new CustomInteger(10));
}
}
JUnitを使ってテストケースを作る際には以下に留意する必要があります。
また、JUnitでは標準的なチェックメソッドが準備されています。それを使うとテストケースを実行するツール側へ結果の通知が可能となることと、自分でテストメソッドの実装が不要となります。詳細は参考サイトを確認してください。
Androidの環境とは相性が悪いようで、Androidのjarを実行環境から抜かないと実行が上手くいきません。(Androidのjarは通常のJAVA VMと互換性のないDalvik VMを使っているから?)また、Androidの実機上で動作させるAndroid拡張JUnitもありますので、それは別途説明します。また後で。
AndroidにおいてAcitivity、Service、ContentProviderなどのコンテキスト情報はシステム提供のライブラリ内で管理されているため、通常のJUnitでは試験が実質的に不可能です。また、Dalvik VM上(エミュレータ、実機)で動作させなければAndroidアプリケーションの試験をしたことになりません。それらの理由からAndroidでは標準のJUnitを拡張したテスト向けのライブラリが提供されています。
JUnitのテストケースを拡張したクラス階層は深いですが、基本的に一番条件にマッチするリーフクラスを使用すれば問題ありません。以下の図にテスト対象と被テスト対象の関係を示しています。
では実際にAndroid向けのJUnitを使ったテストを見てみます。Android向けのJUnitを使用するためにはマニフェストファイルに宣言が必要となります。実アプリケーションにこうしたマニフェストファイルを載せるのは不適切です。そのため、別プロジェクトを作成してテストをします。被テスト対象のプロジェクト(WebAccess)をパッケージcom.suddenAngerSystemに配置した場合の例を見ていきましょう。
テスト実施側のプロジェクトではアクティビティ、アプリケーションは不要のため無しで作成します。
そして、JUnitのテストランナーを拡張したAndroidのテストランナーを使用しますので、それを宣言します。(applicationのラベルなどは不要なので削除しています。)パッケージ名の最後をtestsとしているのに注意してください。(理由は分かりませんが、テストプロジェクトはパッケージの最後にtestsを付けないと動作しないようです)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.suddenAngerSystem.tests"
android:versionCode="1"
android:versionName="1.0">
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.suddenAngerSystem"
android:label="WebAccessのテスト"/>
<uses-sdk android:minSdkVersion="3" />
</manifest>
次に被テスト対象のプロジェクトへの依存関係を設定します。(これを設定しないと被テスト対象のクラスをテスト側で使用できません。)設定の仕方はまずテストプロジェクトのプロパティを選択します
次にjavaのビルドパスを選択し、プロジェクトタブから追加を選択して被テスト対象のプロジェクトを選択します。
次にテスト用のクラスを作成します。まず、アクティビティのWebAccessクラスをテストするTestWebAccessクラスを作成してみます。JUnitのテストケースとの差分はgetActivityでアクティビティが取得できることと、タッチモード、起動インテントの設定になります。また、テスト用のクラスとしてTouchUtilsクラスが提供されています。このクラスはタッチパネル操作などを擬似的に実施する機能を提供しており、GUI操作についても人の手を介さない試験が可能となっています。
package com.suddenAngerSystem.tests; import android.test.ActivityInstrumentationTestCase2; import android.test.TouchUtils; import android.view.View; import com.suddenAngerSystem.WebAccess; //アクティビティのテスト(WebAccessのテスト) public class TestWebAccess extends ActivityInstrumentationTestCase2{ //ActivityInstrumentationTestCase2は暗黙のコンストラクタがないため、必ず定義が必要 public TestWebAccess() { //テスト対象のパッケージ名と super("com.suddenAngerSystem", WebAccess.class); //タッチモードを有効にする場合はgetActivityを最初に起動する前に起動する必要がある setActivityInitialTouchMode(true); //ACTION_MAIN以外でアクティビティを起動したい場合はgetActivityを最初に起動する前に起動する必要がある。 //setActivityIntent(intent); } //JUnitのテストケースのsetUpと同様に各テストメソッド起動前に起動される @Override protected void setUp() throws Exception { } //JUnitのテストケースのtearDownと同様に各テストメソッド起動後に起動される @Override protected void tearDown() throws Exception { } //テストメソッド1 //テストメソッドの構築方法はJUnit標準と変わらない public void testFoo() { //テスト対象のアクティビティはgetActivity()で取得できる。 final WebAccess testTarget = getActivity(); //実際のテスト例 //ボタンを取得 final View go = testTarget.findViewById(com.suddenAngerSystem.R.id.GoButton); //ボタンを押す TouchUtils.tapView(this, go); } //テストメソッド2 //テストメソッドの構築方法はJUnit標準と変わらない public void testBar() { } }
他は特にJUnitのテストケースと変わりません。右クリックでテストメソッド単位の実行も可能です。