自分用のまとめです。
以下のライブラリを使ったJavaのユニットテストの作り方を記載します。
- JUnit(テスティングフレームワーク)
- Mockito(モック作成フレームワーク)
- PowerMock(Mockitoの拡張フレームワーク)
Gradleの設定
必要なファイル(JUnit, Mockito, PowerMock)のダウンロードにはGradleを使いました。
設定ファイルの内容は以下の通りです。
build.gradle
apply plugin: 'java'
repositories {
jcenter()
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.+"
testCompile 'org.powermock:powermock-module-junit4:2.0.0-RC.4'
testCompile 'org.powermock:powermock-api-mockito2:2.0.0-RC.4'
}
テスト対象クラス
テスト対象となるクラスです。
このクラスに対してJUnit, Mockito, PowerMockを使った場合を考えます。
Samole.java
public class Sample {
private Sub sub;
public Sample() {}
public Sample(Sub sub) {
this.sub = sub;
}
public String pubGet() {
return "pubGet";
}
public String pubGet(String suffix) {
return pubGet() + suffix;
}
public String pubGetSubValue() {
return sub.getValue();
}
public String pubGetStaticValue() {
return Static.getValue();
}
private String priGet() {
return "priGet";
}
private String priGet(String suffix) {
return priGet() + suffix;
}
public static class Sub {
private String value;
public Sub(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public static class Static {
public static String getValue() {
return "Static value";
}
}
}
JUnitのみのテストクラス
JUnitのみを使った場合です。
SampleTest.java
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
public class SampleTest {
private Sample instance;
@Before
public void init() {
this.instance = new Sample();
}
@Test
public void pubGet() {
// publicメソッドのテスト
String actual = instance.pubGet();
assertThat(actual, is("pubGet"));
}
@Test
public void priGet() throws Exception {
// privateメソッドのテスト(引数なし)
Method method = Sample.class.getDeclaredMethod("priGet");
method.setAccessible(true);
String actual = (String) method.invoke(instance);
assertThat(actual, is("priGet"));
}
@Test
public void priGet_withArg() throws Exception {
// privateメソッドのテスト(引数あり)
Method method = Sample.class.getDeclaredMethod("priGet", String.class);
method.setAccessible(true);
String actual = (String) method.invoke(instance, "Suffix");
assertThat(actual, is("priGetSuffix"));
}
@Test
public void pubGetSubValue() {
Sample instance = new Sample(new Sample.Sub("test"));
String actual = instance.pubGetSubValue();
assertThat(actual, is("test"));
}
}
JUnit & Mockitoを使ったテストクラス
Mockitoを追加した場合です。
モックを使ってメソッドの内容を書き換えることが出来ます。
SampleForMockitoTest.java
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SampleForMockitoTest {
@Mock
private Sample mock;
@Spy
private Sample spy;
@Before
public void init() {
// @Mockアノテーションのモックオブジェクトを初期化
MockitoAnnotations.initMocks(this);
}
@Test
public void pubGetSubValue() {
// モックを使用しない場合
Sample instance = new Sample(new Sample.Sub("test"));
String actual = instance.pubGetSubValue();
assertThat(actual, is("test"));
}
@Test
public void pubGetSubValue_withMockito() {
// モックを使用した場合は、スタブの値を返す
Sample.Sub mock = mock(Sample.Sub.class);
when(mock.getValue()).thenReturn("mock value");
Sample instance = new Sample(mock);
String actual = instance.pubGetSubValue();
assertThat(actual, is("mock value"));
}
@Test
public void pubGetSubValue_withMockitoMock() {
// mock() はスタブを実装しないとデフォルト値(null)となる
String actual1 = mock.pubGet();
assertThat(actual1, is(nullValue()));
// mock() はスタブを実装するとスタブの値を返す
when(mock.pubGet()).thenReturn("mock value");
String actual2 = mock.pubGet();
assertThat(actual2, is("mock value"));
}
@Test
public void pubGetSubValue_withMockitoSpy() {
// spy() はスタブを実装しないと実際の値を返す
String actual1 = spy.pubGet();
assertThat(actual1, is("pubGet"));
// spy() はスタブを実装するとスタブの値を返す
when(spy.pubGet()).thenReturn("mock value");
String actual2 = spy.pubGet();
assertThat(actual2, is("mock value"));
}
@Test
public void pubGetSubValue_withMockitoAnswer() {
// mock() はスタブを実装するとスタブの値を返す
// Answerを使うとスタブにメソッドの引数を利用することができる
when(mock.pubGet(anyString())).thenAnswer(invocation -> {
String arg1 = (String) invocation.getArguments()[0];
return "mock value " + arg1;
});
String actual = mock.pubGet("suffix");
assertThat(actual, is("mock value suffix"));
}
}
JUnit & Mockito & PowerMockを使ったテストクラス
PowerMockを追加した場合です。
static
メソッドの内容を書き換えることが出来ます。
SamleForPowerMockTest.java
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
@RunWith(PowerMockRunner.class)
@PrepareForTest({Sample.Static.class})
public class SamleForPowerMockTest {
private Sample instance;
@Before
public void init() {
instance = new Sample();
}
@Test
public void pubGetWithStatic() {
// staticメソッドの値をテスト
String actual = instance.pubGetStaticValue();
assertThat(actual, is("Static value"));
}
@Test
public void pubGetWithStatic_withPowerMock() {
// mockStatic() でstaticメソッドがスタブの値を返す
PowerMockito.mockStatic(Sample.Static.class);
// 目的のメソッド以外は元々の挙動としたい場合は代わりに PowerMockito.spy() を使う
// PowerMockito.spy(Sample.Static.class);
PowerMockito.when(Sample.Static.getValue()).thenReturn("PowerMockito value");
String actual = instance.pubGetStaticValue();
assertThat(actual, is("PowerMockito value"));
}
}
まとめ
PowerMockを使うと強力な書き換えを行うことができる。
しかしPowerMockに頼りすぎると本来の目的(拡張性のある実装)を見失い、
カバレッジを稼ぐだけのテストになる危険性も感じたので使い所は考えた方が良さそう。
備考
MockitoとPowerMockの使い分けを参考にさせて頂きました。