PowerMockでprivateもstaticもコンストラクタも単体テストする

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

Javaでの単体テストといえばJUnitだが、テストを実行するには色々と壁がある。
実行したいメソッドが内部で呼び出すメソッドが環境依存で、
テストのために環境を準備しないと実行できない事がある場合などだ。

そこで、モックオブジェクトを作成し、関係ないメソッドについては
実行した事にして先に進む事が出来るようにする方法がよく取られる。
JUnit用に、モックを簡単に作れるようなライブラリも幾つかある。

だが、staticなメソッドや、privateなメソッド、コンストラクタなど、
モックを作るのも難しいようなメソッドもあり、どうしても実行できないようなメソッドも存在する。

そこで、最終兵器「PowerMock」の出番だ。
PowerMockは、上述したような、本来Javaの制限で実行出来なさそうなメソッドですら、
モックとして簡単に実装出来る。これを使えば、ほぼどんなテストでも実行可能と言っていい。

しかも、EasyMockというモックライブラリと連携できるので、非常に使いやすい。
出来る所はEasyMockで実装し、難しい所だけPowerMockを使えばよいのだ。

ということで、PowerMockについて少し説明する。

なお、この説明は俺の経験談に基づいて作られている。
そのため、誤りがあったら指摘をして欲しい。
この記事の最大の目的は、PowerMockの日本語記事を増やす事だから、
指摘はこの記事へのコメントの他、あなたのブログでしていただけるととても有難い。


サンプルにするクラス

今回はサンプルとしてこのクラスをテストする。

  1. public class TestTargetClass {
  2.     private void testTargetMethod(String argA, String argB) {
  3.         AnotherClass npc = new AnotherClass(argA);
  4.         String localValueA = this.privateMethod(argA);
  5.         staticMethod();
  6.     }
  7.  
  8.     //FooExceptionは、独自の例外
  9.     private String privateMethod(String argC) throws FooException {
  10.         //DBへの接続など、テスト環境では実現できない処理
  11.         ・・・
  12.         return privateMethodReturnValue;
  13.     }
  14.  
  15.     public static String staticMethod() {
  16.         //DBへの接続など、テスト環境では実現できない処理
  17.         ・・・
  18.         return result;
  19.     }
  20. }
  21.  
  22. public class AnotherClass {
  23.     AnotherClass(String another) {
  24.     }
  25. }

PowerMockの準備

PowerMockをダウンロードする。今回はEasyMockと併用するので、Donwloadから
「powermock-easymock-junit-<バージョン>.zip」をダウンロードし、展開する。
展開後、Eclipseならばビルドパスを設定するなどして、クラスパスを通しておく。
ただ、個人的にはEasyMockから、
「EasyMock Class Extension 」もダウンロードしておく事をお勧めする。これは、
本来インタフェースからしかモックを作れないEasyMockの機能を拡張し、
クラスからでもモックを作れるようにしてくれる機能だ。
多分PowerMockだけでも同等の事が出来ると思われるが、後述の通りPowerMockは遅いので、
EasyMockだけで済む所はEasyMockを使った方がよい。そのための準備だ。

クラスパスを通したら、テストクラスを作成する。
PowerMockでは、クラスのアノテーションとして、PowerMockを使う事を宣言する必要がある。
また、モックとして使う部分のあるクラスも宣言しておく。

  1. import org.junit.runner.RunWith;
  2. import org.powermock.modules.junit4.PowerMockRunner;
  3.  
  4. @RunWith(PowerMockRunner.class)//PowerMockの利用を宣言
  5. @PrepareForTest({TestTargetClass.class})//モックオブジェクトを作るクラスを宣言
  6. public class TestClass {
  7. ・・・

モックオブジェクトを作る

PowerMockでモックオブジェクトを作る場合、以下のようにする。

  1. PowerMock.createMock(<オブジェクトを作るクラス>);

例えばこのようになる。これで、TestTargetClass型のインスタンスが作られる。

  1. PowerMock.createMock(TestTargetClass.class);

他にも、CreatePartialMockという、一部のメソッドだけモック化するような作り方や、
CreateNiceMockという、予定外のメソッドが呼ばれても気にしないモックを作る方法もある。
詳しくはAPIを見て欲しい所だ。

privateメソッド

テスト対象のメソッドが内部でprivateメソッドを呼んでいる時、
そのprivateメソッドの呼出を検知する事ができる。
privateメソッドの呼出を検知する場合、以下のように記述する。

  1. instance = PowerMock.createPartialMock(<モックにするクラス>, new String[]{<メソッド名1>, <メソッド名2>,・・・});
  2. PowerMock.expectPrivate(<インスタンス>, <メソッド名>, <引数1>, <引数2>,...).andReturn(<戻り値>);

サンプルのTestTargetClass#privateMethodであれば、以下のようになる。

  1. instance = PowerMock.createPartialMock(TestTargetClass.class, new String[]{"privateMethod"});
  2. PowerMock.expectPrivate(instance, "privateMethod", argA).andReturn("powermock-result");
  3. PowerMock.replay(instance);

staticメソッド

staticメソッドを呼び出す時は、

  1. PowerMock.mockStaticPartial(<staticメソッドを呼び出すクラス>, <メソッド名1>, <メソッド名2>,・・・);
  2. EasyMock.expect(<staticメソッド呼出>).andReturn(<戻り値>);
  3.  
  4. PowerMock.replay(<staticメソッドを呼び出すクラス>);

サンプルのTestTargetClass#staticMethod呼出の監視は、以下のようになる。

  1. PowerMock.mockStaticPartial(TestTargetClass.class, "staticMethod");
  2. EasyMock.expect(TestTargetClass.staticMethod()).andReturn("powermock-result");
  3.  
  4. PowerMock.replay(<staticメソッドを呼び出すクラス>);

なお、staticメソッド呼出の監視時に指定するクラスは、
「staticメソッドを定義しているクラス」であることに注意する。
クラスAで定義されているstaticメソッドを子クラスB内で呼び出す場合でも、
モックの監視指定はクラスAになる。この点は注意が必要だ。

コンストラクタ

コンストラクタが呼び出されている事も、モックで確認できる。

  1. PowerMock.expectNew(<コンストラクタを監視したいクラス>, <引数1>, <引数2>,・・・).andReturn(<返すインスタンス>);

サンプルのAnotherClassであれば、以下のようになる。

  1. AnotherClass acMock = PowerMock.createMock(AnotherClass.class);
  2. PowerMock.expectNew(AnotherClass.class, argA).andReturn(acMock);
  3.  
  4. PowerMock.replay(AnotherClass.class);

勿論、@PrepareForTestに、AnotherClass.classを追加するのを忘れずに。

なお、返すインスタンスはPowerMockで作ろうが、EasyMockで作ろうが、
本物のインスタンスを普通に作ろうが、関係なく動く。

ログクラス

PowerMockはクラスローダーを標準のものと入れ替えて使っているようで、
log4jを使っていると動作がおかしくなってしまう。
PowerMockのFAQの4番に載っているが、
アノテーション「@PowerMockIgnore」を使用する事で、
ログを正しく動作させる事ができる。

  1. import org.junit.runner.RunWith;
  2. import org.powermock.modules.junit4.PowerMockRunner;
  3. import org.powermock.core.classloader.annotations.PowerMockIgnore;
  4.  
  5. @RunWith(PowerMockRunner.class)//PowerMockの利用を宣言
  6. @PrepareForTest({TestTargetClass.class})//モックを使うクラスを宣言
  7. @PowerMockIgnore({"org.apache.log4j.*"})//PowerMockのクラスローダから外すクラス・パッケージの宣言
  8. public class TestClass {
  9. ・・・

なお、自分でログクラスを作っている場合、そのパッケージも宣言しておく。
「”org.tarotaro.java.mylog.MyLogAppender”」を作った場合、以下のようになる。

  1. @PowerMockIgnore({"org.apache.log4j.*", "org.tarotaro.java.mylog.*"})

PowerMockのデメリット

もちろん、PowerMockにもデメリットがある。
最大のデメリットは重く、遅い事だ。
PowerMockは重い。とにかく重い遅い。テストを実行しても中々動き出さない。
これが最大の難点だ。
あと、凄すぎてどうやって動いてるのか良くわからない。
謎だ。
本当に単体テストできてるのか?実はテストしたいメソッドまでモックでは?
という疑いが消えない。
まぁそんな時は、一応デバッグ実行して試してみよう。

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

フォローする

スポンサーリンク
スポンサーリンク