JUnitでどうしてもprivateメソッドをテストしなければならない時のために。
実装方法を定期的に忘れるので、一回しっかり記事で書けば忘れないだろう。という動機で書き残しておきます。
一般的なケース
以下のようなプライベートメソッドを持つクラスをテスト対象に考えます。
public class MyClass {
private String introduceMyself(String name){
return "my name is " + name + ".";
}
}
MyClassのprivateなintroduceMyselfメソッドに対するテストは、以下のようにリフレクションを使うことで行えます。
import com.example.MyClass;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
public class MyClassTest {
@Test
void testIntroduceMyself01() throws Exception {
MyClass targetClass = new MyClass();
// テスト対象メソッドの情報を取得する
Method method = MyClass.class.getDeclaredMethod("introduceMyself", String.class);
// メソッドをアクセス制限を解除
method.setAccessible(true);
// メソッド呼び出し
String selfIntroduction = (String) method.invoke(targetClass,"John");
// 結果をアサーション
assertThat(selfIntroduction).isEqualTo("my name is John.");
}
}
privateメソッドがスローする例外のアサーション
正常系のテストケースは上記で問題ないのですが、privateメソッドから例外がスローされるケースは注意が必要です。
Method#invoke内で発生した例外は、InvocationTargetExceptionクラスにラップされてしまいます。その為、原因例外を取り出してからアサーションする必要があります。
public class MyClass {
private String introduceMyself(String name){
// 引数が空文字の場合は例外をスローする
if(name.isEmpty()){
throw new IllegalArgumentException("argument must not be empty.");
}
return "my name is " + name + ".";
}
}
public class MyClassTest {
@Test
void testIntroduceMyself02() throws Exception {
MyClass targetClass = new MyClass();
Method method = MyClass.class.getDeclaredMethod("introduceMyself", String.class);
method.setAccessible(true);
try {
method.invoke(targetClass, "");
fail("this code should not has been reached.");
}catch (InvocationTargetException e) {
// getCauseで原因例外を取り出してからアサーション
assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
}
}
}
privateメソッドの引数にnullを渡す
リフレクションで呼び出すメソッドの引数としてnullを渡したい場合は、nullを要素として持つObject配列を渡す必要がある為、こちらも注意が必要です。
method.invoke(targetClass, new Object[]{null});
// これではダメ
method.invoke(targetClass, null);
そもそもprivateメソッドを単独でテストすべきではない
ここまで書いといてあれですが、privateメソッドは原則としてユニットテストすべきではありません。
privateメソッドは、言ってみればpublic(protected)メソッドから切り出された内部処理です。内部処理を強引にリフレクションでテストすることで、テストコードが内部実装に依存することになり、リファクタリングや機能追加の際に障壁となります。
適切に設計されたprivateメソッドであれば、呼び出し元のメソッド経由で網羅性はテスト可能です。
仮にprivateメソッドを個別で呼び出さなければ通らない分岐があるとしたら、それは実質的にデッドコードですから、実装を見直したほうが良いでしょう。
まとめ
- privateメソッドを呼び出すにはリフレクションが必要
- 例外のテストは、InvocationTargetExceptionからgetCauseする事
- invokeにnullを渡す場合は、nullを要素として持つObject配列を渡す
- privateメソッドをテストする必要が出たら、設計を見直してみること
- privateメソッドは、呼び出し元のメソッド経由でテストするのが原則