Edited at

JUnit4のすすめ

More than 3 years have passed since last update.


JUnit3からJUnit4への移行

JUnit3のコードからJUnit4に移行するとどんなコードになるかを簡単に説明。

基本的な部分を中心にピックアップする。


テストケースの基本構造


public class Foo {

public void method1() {

}

public void method2() {

}

}

上記のクラスに対するJUnit3のテスト用クラスの基本構造


public class FooTest extends TestCase { // TestCaseのサブクラスにする必要がある。

@Override
protected void setUp() {
super.setUp();
// セットアップ処理
}

@Override
protected void tearDown() {
super.tearDown();
// 後処理
}

// テストケースとなるメソッドは"test"から始める必要がある
public void testMethod1() {

}

public void testMethod2() {

}

}

JUnit4での置き換え


@RunWith(JUnit4.class) // テストランナーを指定
public class FooTest { // TestCaseのサブクラスである必要はない

@Before // setUpのオーバーライドではなく、任意のメソッドにアノテーションをつける
protected void setUp() {
// セットアップ処理
}

@After // tearDownのオーバーライドではなく、任意のメソッドにアノテーションをつける
protected void tearDown() {
// 後処理
}

// テストケースとなるメソッドにTestアノテーションをつける。名前は自由。
@Test
public void method1DoSomething() {

}

@Test
public void testMethod2() {

}

}


検証方法


public class Foo {

public static int sum(int a, int b) {
return a + b;
}

public static boolean returnTrue() {
return true;
}

public static Object returnNull() {
return null;
}
}

上記のメソッドに対するJUnit3のテスト


public void testSum() {
int c = Foo.sum(1, 2);
assertEquals(3, c);
}

public void testReturnTrue() {
assertTrue(Foo.returnTrue());
}

public void testReturnNull() {
assertNull(Foo.returnNull());
}

JUnit4での置き換え


// 以下のクラスのstaticメソッドを頻繁に利用するのでimportしておくと良い。
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

@Test
public void sumReturns3() {
int c = Foo.sum(1, 2);
assertThat(c, is(3));
}

@Test
public void returnTrueReturnsTrue() {
assertThat(Foo.returnTrue(), is(true));
}

@Test
public void returnNullReturnsNull() {
assertThat(Foo.returnNull(), is(nullValue()));
}


例外発生のテスト


public class Foo {

public void doSomething(String arg) {

if (arg == null) {
throw new IllegalArgumentException("arg must not be null");
}

// do something.
}
}

上記のプログラムに対するJUnit3のテスト


public void testDoSomething() {

Foo foo = new Foo();

try {
foo.doSomething(null);
fail("No Exception");
} catch(IllegalArgumentException e) {
assertTrue(true);
}
}

JUnit4での置き換え


@Test(expected = IllegalArgumentException.class) // アノテーションで発生する想定の例外を指定できる。
public void doSomethingThrowsIllegalArgumentExceptionWithNull() {
Foo foo = new Foo();
foo.doSomething(null); // テストのコード上で例外の発生を検知する必要がない。
}


Enclosedランナー

テストケースをグループ化するのに利用する。同じような特徴を持つテストケースをまとめることができる。


public class Foo {

private int age;

public Foo(int age) {
this.age = age;
}

public boolean isAdult() {
return age >= 20;
}

public int getPrice() {
if (isAdult()) {
return 2000;
} else {
return 1000;
}
}

}

Enclosedランナーを使って、上記のクラスに対して以下のようなテストを書くことができる。

テストを実行すると、自動的にインナークラスがそれぞれテストとして実行される。


@RunWith(Enclosed.class) // トップレベルのクラスにEnclosedを指定する。
public class FooTest {

// コンストラクタの引数が15の場合のテスト
public static class TeenTest {

private Foo foo;

@Before
public void before() {
foo = new Foo(15);
}

@Test
public void isAdultReturnsFalse() {
assertThat(foo.isAdult(), is(false));
}

@Test
public void getPriceReturns1000() {
assertThat(foo.getPrice(), is(1000));
}

}

// コンストラクタの引数が25の場合のテスト
public static class AdultTest {

private Foo foo;

@Before
public void before() {
foo = new Foo(25);
}

@Test
public void isAdultReturnsTrue() {
assertThat(foo.isAdult(), is(true));
}

@Test
public void getPriceReturns2000() {
assertThat(foo.getPrice(), is(2000));
}

}

}


Theoriesランナー

パラメータ等の組み合わせのテストを作成するのに利用する。

DataPointアノテーションを指定すると、一致する型の引数に自動で割り振られ、テストが実行されます。


@RunWith(Theories.class)
public class FooTest {

@DataPoint
public static final String ARG1 = "arg1";

@DataPoint
public static final String ARG2 = "arg2";

@DataPoint
public static final int ARG3 = 1;

@DataPoint
public static final int ARG4 = 2;

@Theory
@Test
public void stringDataPointTest(String arg) {
System.out.println("String ARG = " + arg);
}

@Theory
@Test
public void intDataPointTest(int arg) {
System.out.println("int ARG = " + arg);
}

}

上記のテストを実行すると、以下のような結果となります。


String ARG = arg1
String ARG = arg2
int ARG = 1
int ARG = 2

さらにDataPointsアノテーションを使用して、配列で引数を定義することもできます。


@RunWith(Theories.class)
public class FooTest {

@DataPoints
public static final String[] ARGS = {
"arg1", "arg2", "arg3"
}

@Theory
@Test
public void stringDataPointTest(String arg) {
System.out.println("String ARG = " + arg);
}

}

上記のテストを実行すると、以下のような結果となります。


String ARG = arg1
String ARG = arg2
String ARG = arg3


AndroidでのJUnit4


導入

Android StudioでのJUnit4の導入方法まとめ。

build.gradleに必要な設定を追加していきます。

便利なのでMockitoも入れておきます。

いろいろなライブラリを入れたり、自身がライブラリだったりしてメソッド数が増えてくるとMulti-dexを利用する必要が出てきます。なのでそれにも対応させておきます。

android {

defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // Android用のテストランナーを指定する
multiDexEnabled true
}

dexOptions {
preDexLibraries false
javaMaxHeapSize "2g" // メモリが足りなくなったなどのエラーが出た場合に設定する。
}

}

dependencies {
androidTestCompile 'com.android.support.test:runner:+'
androidTestCompile 'com.android.support.test:rules:+'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'com.android.support:multidex:1.0.0'
}


Contextの取得

テストコード中でAndroidのContextオブジェクトが必要になった場合は以下のようにして取得することができる。


Context context = InstrumentationRegistry.getContext();
context.startActivity(intent);


Multi-dex対応

Multi-dexを利用する場合はApplicationクラスに手を入れる必要があります。

以下のようにApplicationクラスでMultiDex#install(Context)メソッドを呼び出します。あとはAndroidManifest.xmlファイルに追記してください。

public class TestApplication extends Application {

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}


書籍

以下の本は必読。

上で紹介した機能やランナー、他の機能の詳細について色々かいてあります。

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)


追記


2015/10/21

ART vmの場合、dexmakerがうまく動かない不具合があったようです。

dexmaker-mockito1.2では修正されているようなので、こちらを使用するように記述を修正しました。

参考 : Mockito+dexmakerはART; Android Runtimeでも使えるよ