28
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JUnitでprivateメソッドのテストをする場合に注意すること

Last updated at Posted at 2018-04-13

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メソッドは、呼び出し元のメソッド経由でテストするのが原則
28
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?