JUnit4のすすめ


# JUnit3からJUnit4への移行

JUnit3のコードからJUnit4に移行するとどんなコードになるかを簡単に説明。
基本的な部分を中心にピックアップする。

## テストケースの基本構造

```java

public class Foo {
	
	public void method1() {
	
	}
	
	public void method2() {
	
	}

}


```

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

```java

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での置き換え

```java

@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() {
	
	}
	
}


```

## 検証方法

```java

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のテスト

```java

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での置き換え

```java

// 以下のクラスの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()));
}

```

## 例外発生のテスト

```java

public class Foo {

	public void doSomething(String arg) {
		
		if (arg == null) {
			throw new IllegalArgumentException("arg must not be null");
		}
		
		// do something.
	}
}


```

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

```java

public void testDoSomething() {

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


```

JUnit4での置き換え

```java

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

```

# Enclosedランナー

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

```java

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ランナーを使って、上記のクラスに対して以下のようなテストを書くことができる。
テストを実行すると、自動的にインナークラスがそれぞれテストとして実行される。

```java

@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アノテーションを指定すると、一致する型の引数に自動で割り振られ、テストが実行されます。

```java

@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アノテーションを使用して、配列で引数を定義することもできます。

```java

@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オブジェクトが必要になった場合は以下のようにして取得することができる。

```java

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

```


## Multi-dex対応

Multi-dexを利用する場合はApplicationクラスに手を入れる必要があります。
以下のようにApplicationクラスでMultiDex#install(Context)メソッドを呼び出します。あとはAndroidManifest.xmlファイルに追記してください。

```java
public class TestApplication extends Application {

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



# 書籍

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

[![JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)](http://ecx.images-amazon.com/images/I/51TaOch1LJL._SX351_BO1,204,203,200_.jpg)](http://www.amazon.co.jp/JUnit%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-~%E4%BD%93%E7%B3%BB%E7%9A%84%E3%81%AB%E5%AD%A6%E3%81%B6%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E6%8A%80%E6%B3%95-WEB-PRESS-plus/dp/477415377X)


# 追記

## 2015/10/21

ART vmの場合、dexmakerがうまく動かない不具合があったようです。
dexmaker-mockito1.2では修正されているようなので、こちらを使用するように記述を修正しました。

参考 : [Mockito+dexmakerはART; Android Runtimeでも使えるよ](http://d.hatena.ne.jp/esmasui/20140812/1407823776)