Edited at

私は如何にして JMockit の returns(Object) を安全に置換したか?

テストで使っているJMockitというモックライブラリが古いバージョンだったので、最新バージョンにアップデートする作業をしたのですが、その際にちょっとしたハックをしたので紹介します。

なお、JMockit自体の変更点などについては別記事にしているので、JMockitを使っている方はそちらもご覧ください(→JMockit と共に生きる者ためのメモ - Qiita


どんな問題があったのか?

JMockit(の旧バージョン)では以下のように Expectations のインスタンスイニシャライザでメソッドを差し替えます。戻り値は result または returnsで指定します。

new Expectations() {{

  intList.get(0); result = 1; // intList.get(0) が常に1を返すようになる

  intList.get(0); returns(1); // ↑ の別記法(1.30で廃止)

  intList.get(1); returns(-1, 1); // intList.get(1) が、-1 と 1 を交互に返すようになる
}};

ここで、1引数版returns(v)は JMockit 1.30 で廃止されてしまいました。

そのため、テストコード中の returns(v)result = v に書き換えなくてはなりません。

まず、sed で置換しようとしましたが、returnsには1引数版と2引数以上版があるので単純に置換することはできません。

sed -e 's/returns/result =' // 2引数版も置換してしまう。-e 以外の引数は省略

正規表現を工夫したりもしましたが「returnsの引数として別のメソッド呼び出しを指定する」コードがある等して、上手くいきませんでした。

次に IntelliJ のリファクタリング機能を探しましたが、


  • インライン化

  • メソッド名変更

などはあるのですが「メソッド呼び出しを代入文に変更」という変更はできませんでした1


どうやって解決したか?

まず、returns の呼び出しを自作クラスのメソッドの呼び出しに置換しました。

sed -e 's/returns(/test.MyDummy.returns(/g'

テストコードはこんな形になります。

new Expectations() {{

  intList.get(0); test.MyDummy.returns(1); // returns が自作メソッドのものに置換されている
  intList.get(1); test.MyDummy.returns(-1, 1); // 2引数版も置換される
}};

さらに、自作クラスに以下のようにメソッドを定義します。この時点で、JavaコードとしてはValidになるので、IntellJのリファクタリング機能を使えるようになります。

class MyDummy {

public static void returns(Object o) {
result = o;
}

public static void returns(Object o, Object o2, Object... args) {
}
}

IntelliJ で、MyDummy.returnsにカーソルを合わせ Refactor This → inline でインライン化します。

すると、IntelliJ は、メソッドオーバーロードを理解しているので、1引数版の returns のみをインライン化してくれます。

new Expectations() {{

  intList.get(0); result = 1;
  intList.get(1); test.MyDummy.returns(-1, 1);
}};

これで「1引数のreturnsresult」は完了です。残った test.MyDummy.returns は、再びsed で置換して、returnsに戻します。

sed -e 's/test.MyDummy.returns(/returns(g'

これで、1引数のreturnsのみが書き換えられた状態になりました。

new Expectations() {{

  intList.get(0); result = 1;
  intList.get(1); returns(-1, 1);
}};

かかった時間は、考える時間も含めて1・2時間ほどでした(測ってないけど多分)。returns は何千箇所もあったので、手作業では何日もかかったことでしょう。

また、使った変換は全て機械的なものだったので、バグは生じませんでした(本当にバグっていないかは、実際にテストをコンパイル・実行して確認できました)。





  1. 商用版では出来たりするかもしれませんが、私が使っていたIntelliJ IDEAでは出来ませんでした。