北村由衣のブログ

jUnitでprivateメソッドをテストする

privateメソッドのテストはひと手間かかる

Java環境でメソッド単位の挙動をテストするのに便利なjUnitですが、 クラス内のprivateメソッドは直接呼び出せないため、単純にはテストができません

//テスト対象
class Target{
   private void target(){
      throw new RuntimeException();
   }
}

//理想のテストコード
class TargetTest{
   @Test
   public void testTarget(){
      Target t = new Target();
      assertDoesNotThrow( ()->t.target() );	//コンパイルエラー
   }
}

ここで登場するのが、リフレクション(reflection)です

privateメソッドのテストコード

サンプルコードは以下の通りです

//テスト対象
class Target{
   private int target(int arg){
      return arg;
   }
}
 
//テストコード
class TargetTest{
   /** 可視度が private のメソッドを呼び出す
    * @param instance 呼び出すメソッドを持っているクラスのインスタンス
    * @param arg 呼び出すメソッドへ渡す引数
    */
   private int testablePrivateMethod(Target instance, int arg){
       int result;
       try {
           Method method = Target.class.getDeclaredMethod("target", int.class);
           method.setAccessible(true);

           result = (int) method.invoke(instance, arg);
       } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
           throw new RuntimeException(e);
       }
       return result;
   }

   @Test
   public void testTarget(){
      Target t = new Target();
      int arg = 27;
      assertEquals(arg, testablePrivateMethod(t, arg));	// success
   }
}

GitHubにサンプルコードをアップしてあります

テストコード解説

テスト対象はシンプルな引数を1つ持ち、値を返す仕様のprivateメソッドとしました
staticや引数の個数等への対応は後述しますのでご安心を

テストクラスに、「privateメソッドを呼び出すメソッド」を定義します。testablePrivateMethodとしましたが、名称はお任せします。 普段の私は、メソッドhogehogeを呼び出すメソッドはcallHogehogeみたいにしますが、 標準となるような命名規則は無いかと思われます。
引数は、「テスト対象のインスタンス」と、「メソッドに渡す引数」です。

17行目でテスト対象のprivateメソッドを取得します。

Method method = Target.class.getDeclaredMethod("target", int.class);

Target.classはテスト対象のクラスにしてください。
getDeclaredMethodの引数は、「メソッド名」「引数のclass」です。引数で指定した条件に合致するメソッドが、 テスト対象のクラスから探索されて、method変数に格納されます

実際に呼び出す処理がinvokeメソッドです

result = (int) method.invoke(instance, arg);

invokeは戻り値がObject型なので、取得したい型にキャストします

テスト対象のパターンによる変化

引数の個数、ゼロから複数

//テスト対象
class Target{
   private double target(int arg, double arg2){
      return arg + arg2;
   }
}

//テストコード
class TargetTest{
   private int testablePrivateMethod(Target instance, int arg, double arg2){
       int result;
       try {
           Method method = Target.class.getDeclaredMethod("target", int.class, double.class);
           method.setAccessible(true);

           result = (int) method.invoke(instance, arg, arg2);
       } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
           throw new RuntimeException(e);
       }
       return result;
   }
}

getDeclaredMethod,invokeは引数の個数が0を含む任意個数が許容されています。
よって、テスト対象のメソッドの引数の個数に合わせて、無記載から必要数の列挙まで、なんとでも認められます

Method method = Target.class.getDeclaredMethod("target");
method.setAccessible(true);
result = (int) method.invoke(instance);

staticメソッドのテスト

//テスト対象
class Target{
   private static void target(int arg){
      System.out.print(arg);
   }
}

//テストコード
class TargetTest{
   /** 可視度が private のstaticメソッドを呼び出す
    * @param instance 呼び出すメソッドを持っているクラスのインスタンス
    * @param arg 呼び出すメソッドへ渡す引数
    */
   private void testablePrivateMethod(int arg){
       try {
           Method method = Target.class.getDeclaredMethod("target", int.class);
           method.setAccessible(true);

           method.invoke(Target.class, arg);
       } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
           throw new RuntimeException(e);
       }
   }

   @Test
   public void testTarget(){
      Target t = new Target();
      int arg = 27;
      assertEquals(arg, testablePrivateMethod(arg));
   }
}

staticなメソッドについてはインスタンス化の手間が無い分、多少シンプルになっています。 invokeメソッドの引数もインスタンスではなく、テスト対象のクラス自体を渡します

staticのバージョンもGitHubにサンプルコードをアップしてあります

後記

初めてのJavaの技術記事になりました。今回は特段バージョンの影響が無さそうなネタなので 前提環境とかは省略してしまいましたが、きちんと検証してから投稿したいものです

一度組み方が分かってしまえばいつでも活用できる物なので、自分用の備忘録としてもまとめた次第です。
わたしの公開しているプログラムではMinecraftのプラグイン とかで組み込んでいたりしますが、使う都度「どうだっけー」と探すのも面倒で…。

Javaプログラマの誰かのお役に立てば良いな、と思います


このエントリーをはてなブックマークに追加


コメントはこちらからお寄せください