LoginSignup
6
9

More than 5 years have passed since last update.

private な interface を引数にもつ private なメソッドをテストした話

Last updated at Posted at 2016-06-10

前提

  • Java 8
  • 自分はJava初心者

コメント欄にてこういうもっといい方法があるよ、などのご報告お待ちしてます。

privateなメソッドのテストはつらい

/*
例えばHogeというクラスに
int foo(int i) {
  return i+i;
}
という関数があったとする
*/

Hoge hoge = new Hoge();
Method m = Hoge.class.getDeclaredMethod("foo", int.class);
m.setAccessible(true);
int ret = (int)m.invoke(hoge, 10); // -> ret == 20

でもこれだけなら各ライブラリのお世話になるなどすれば大した事がない(気がする)。

privateなメソッドの引数がprivateなinterfaceだったらもっとつらい

こちらのページが近い事をしていたので大変参考になった。→技術見聞録 - 外部提供のInterfaceをインスタンス化する

以下のようなクラスがある事にする。
(今回の例のインタフェースは関数型インタフェース)

public class Hoge {
  // クラス内からはこのようにアクセス可能
  public int boo(int i) {
    return foo(i, (int i) -> i+i);
  }

  private int foo(int i, InnerInterface innerInterfaceInstance) {
    return innerInterfaceInstance.apply(i);
  }

 @FunctionalInterface // なくてもいいけど
  private interface InnerInterface {
    int apply(int i);
  }

}

もしインナークラスなら、getDeclaredConstructorからのnewInstanceとかやっちゃうことが一応可能なのですが、インナーインタフェース(?)だと難しい。

これをテストするコードが以下。
これに至るまで失敗が無限にありましたが注意点は後述。

@Test
public void testFoo() throws Exception {
  Hoge hoge = new Hoge();

  // Interface内部の処理を移譲されるcallback
  class InterfaceHandler implements InvocationHandler {
    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        return getClass().getMethod(name, method.getParameterTypes()).invoke(this, args);

    public int apply(int i) {
      // apply()にしてほしい内容を書く
      // 例:
      return i * i;
    }
  }

  // パラメータの型の指定が大変なのでforループで名前から探す
  Method fooDummy = null;
  for (Method m : Hoge.class.getDeclaredMethods()) {
    if (m.getName().equals("foo")) {
      fooDummy = m;
    }
  }

  // 黒魔術的設定
  Class<?> interfaceClass = foo.getParameterTypes()[1]; // 2個目の引数
  ClassLoader loader = interfaceClass.getClassLoader();
  Class<?>[] interfaces = new Class<?>[] {interfaceClass};
  InvocationHandler handler = new InterfaceHandler();
  Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, handler);

  fooDummy.setAccessible(true);

  // 実行!
  int ret = (int)fooDummy.invoke(hoge, 10, proxyInstance);

  assertEquals(ret, 100) // -> passed

}

java.lang.reflect.InvocationHanlder
java.lang.reflect.Proxy
の2つを利用して、interfaceの関数をこっちで指定してやる感じですね。

注意点

  • インタフェースの型が必要になるのでforループ使って名前でmethodを取り出す。(もっといい方法もあるかもしれない)
  • ちなみにHoge#boo()みたいにラムダ渡すと怒られます。invokeの引数がObjectを要求しているからでしょうか?

結論

package privateにしような

どうやら、AOP(アスペクト指向プログラミング)の実現のために使われるるらしい(下記参照)

その他の参考になるサイト

動的プロキシによるアスペクトとしての実装
java.lang.reflect.Proxyの使い方(1)

6
9
3

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
6
9