The HIRO Says

If you smell what The HIRO is cooking!!!

リフレクションの呼び出し回数を気持ち減らしてみる

単体テストを作成していると、たまにテスト対象クラスの private 変数を検証したくなることってありませんか?


そういう時は、例えば以下のように、リフレクションを使いますよね。

    // target は、テスト対象クラスのインスタンスと考えて下さい。
    // private 変数 "size" の Field を取得する。
    Field field = target.getClass().getDeclaredField("size");

    // private 変数にアクセスできるようにする。
    field.setAccessible(true);

    // private 変数 "size" の値を取得する。
    Integer size = (Integer) field.get(target);


でも、リフレクションはコストのかかる処理。
何度も呼び出すと、処理がどうしても遅くなってしまいがちですよね。
(「テストだからいーじゃん!」っていう意見は今は却下!)


一方で、private 変数を検証する場合、1つのテストメソッド内で何度もチェックしたくなったりするものです。
(最近、1メソッド34回というケースがありました。)


リフレクションの回数を減らしながら、なおかつ何度もアクセスする方法はないか?
そんな都合のよいことを考えていたら、1つ方法を見つけました。


リフレクション回数を減らす方法

  1. リフレクション対象の Field を、private なインスタンス変数として定義する。
  2. Field へ初回にアクセスするときだけ、Field をリフレクションで呼び出すようにする。


以下、具体例を示します。

    /**
     * private 変数 "size" 用の Field
     */
    private Field size;


    /**
     * リフレクションで size を取得するメソッド(@param などは省略)
     */
    private int getSize(Xxx target) throws NoSuchFieldException, IllegalAccessException {

        if (this.size == null) {
            // 初回アクセス時だけリフレクションを行う。
            // (毎回 null チェックをやることになるが、毎回リフレクションするよりはまし。)
            this.size = target.getClass().getDeclaredField("size");
            this.size.setAccessible(true);
        }

        return (Integer) field.get(target);
    }

要は、(java.lang.reflect.)Field は参照型なので、一度初期化しておけば、継続して中身を見つづけられるということです。


検討すべきポイント

Field をインスタンス変数として定義してもよいのか?

Field を全テストケースで参照する場合は、今回のようにインスタンス変数とすることが好ましいと思います。
一方、一部のテストケースでのみ使う場合は、ローカル変数で定義した方がよいかもしれません。
(ちなみに、この方法を最初に検討したケースでは、Field を全テストケースで参照したため、インスタンス変数として定義しました。)

どの位の頻度の場合、この方法を採用すべきか?

1メソッドで10回も同じものを呼ぶのであれば、この方法を採用してもよいのではと思いますが…
さすがにケースバイケースでしょうね。
暇な時に NetBeans あたりでパフォーマンス調査してみます。