1. kazurof

    投稿

    kazurof
タイトルの変更
+mockitoとJMockitについてのメモ
タグの変更
本文の変更
Source | HTML | Preview
@@ -0,0 +1,206 @@
+mockitoとJMockitについて簡単にまとめました。両者ともJavaのユニットテスト向けモックフレームワークなのですが、使い方は全然違います。さわりをちょっと調べただけです。有識者のコメント歓迎します。
+
+# 対比
+
+| | mockito | JMockit |
+|-----------:|:--------------------:|:-------------------:|
+| URL | http://mockito.org/ | http://jmockit.org/ |
+| パッケージ名| org.mockito.* | mockit.* |
+| API | http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html | http://jmockit.org/api1x/index.html |
+
+プロダクト名、パッケージ名ともに紛らわしいので検索するときは注意。
+
+# コードサンプル
+
+ `Logic.java`が`Calc.java`を呼んでいて、CalcをモックしてLogicの動作を確認したいとします。
+
+
+## mockitoでのサンプル
+
+``` LogicTestInMockito.java
+package org.example;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.when;
+
+public class LogicTestInMockito {
+
+ @Mock
+ Calc calc;
+
+ @InjectMocks
+ Logic logic;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testAsUsual() throws Exception {
+ logic.calc = new Calc();
+ int[] result = logic.getDivisibleNumbers();
+ assertTrue(result[0] % result[1] == 0);
+ }
+
+ @Test
+ public void testInMockito() throws Exception {
+ when(calc.remainder(anyInt(), anyInt())).thenAnswer(i -> {
+ // 公式ドキュメントには書いてありませんが、ここはラムダが使えます。
+ return ((Integer) i.getArguments()[1]) == 5 ? 0 : 1;
+ });
+ int[] result = logic.getDivisibleNumbers();
+ assertTrue(result[1] == 5);
+ }
+}
+```
+
+## JMockitでのサンプル (Expectations APIでの例)
+
+``` LogicTestInJMockit.java
+package org.example;
+
+import mockit.Expectations;
+import mockit.Mocked;
+import mockit.Verifications;
+import mockit.integration.junit4.JMockit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+@RunWith(JMockit.class)
+public class LogicTestInJMockit {
+
+ @Test
+ public void testAsUsual() throws Exception {
+ Logic logic = new Logic(new Calc());
+ int[] result = logic.getDivisibleNumbers();
+ assertTrue(result[0] % result[1] == 0);
+ }
+
+
+ @Test
+ public void testInJMockit(@Mocked Calc calc) throws Exception {
+ new Expectations() {{
+ calc.remainder(anyInt, 5);
+ result = 0;
+ calc.remainder(anyInt, anyInt);
+ result = 1;
+ }};
+ Logic logic = new Logic(calc);
+ int[] result = logic.getDivisibleNumbers();
+ new Verifications() {{
+ calc.remainder(anyInt, 5);
+ minTimes = 1;
+ }};
+ assertTrue(result[1] == 5);
+ }
+}
+```
+
+## JMockitでのサンプル (Mockups APIでの例)
+
+
+``` LogicTestInJMockitMockups.java
+package org.example;
+
+import mockit.Mock;
+import mockit.MockUp;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class LogicTestInJMockitMockups {
+
+ // このテストが、testInJMockitWithMockups() の後に実行されると
+ // モックしていないCalcを使っているつもりがモックされたものになってしまう様子。
+ @Test
+ public void testAsUsual() throws Exception {
+ Logic logic = new Logic(new Calc());
+ int[] result = logic.getDivisibleNumbers();
+ assertTrue(result[0] % result[1] == 0);
+ }
+
+
+ @Test
+ public void testInJMockitWithMockups() throws Exception {
+ new MockUp<Calc>() {
+ @Mock()
+ public int remainder(int dividend, int divisor) {
+ return divisor == 5 ? 0 : 1;
+ }
+ };
+ Logic logic = new Logic(new Calc());
+ int[] result = logic.getDivisibleNumbers();
+ assertTrue(result[1] == 5);
+ }
+}
+```
+
+## モック対象のコード
+
+``` Calc.java
+package org.example;
+
+public class Calc {
+ int remainder(int dividend, int divisor) {
+ return dividend % divisor;
+ }
+}
+```
+
+## テスト対象のコード
+
+``` Logic.java
+package org.example;
+
+public class Logic {
+ Calc calc;
+
+ Logic(Calc calc) {
+ this.calc = calc;
+ }
+ /**割り切れる数字のペアをアトランダムに返す。*/
+ int[] getDivisibleNumbers() {
+ while (true) {
+ int candidate = (int) (Math.random() * 100) + 1;
+ int candidate2 = (int) (Math.random() * 10) + 1;
+ if (calc.remainder(candidate, candidate2) == 0) {
+ return new int[]{candidate, candidate2};
+ }
+ }
+ }
+}
+```
+
+
+# 寸評
+
+mockitoはモックの挙動を `when()` `doReturn()` `thenAnswer()` で決めていくスタイル。 チェーン表記が綺麗とも煩くなるとも言われている様子。素朴だがたくさんやると限界があるかもしれません。
+
+JMockitはAPIにクセがあるが、慣れればOKという記事たちらほら見つかります。(なにげなくインスタンスイニシャライザ使ってて慣れるのに時間がかかるひとはいそう。)個人的には Mockups APIの場合、モックするメソッドに対してコンパイラのチェックがかからないことや、`new MockUp<Calc>(){...} `した後は、モックしていない`Calc`が使えなくなるなど、学習曲線がちょっと厳しいと思われました。先の例だと`LogicTestInJMockitMockups.java`は、テストメソッドの順序次第で`testAsUsual()`が失敗します。また、mockitoよりも呼び出し回数のテストとか実引数をモックが取得できるとか細かい機能が充実していそうな様子です。(`mockit.Invocation` `mockit.Mock#invocations`など。)
+
+# 私の結論
+
+学習曲線が低そう。ラムダが使えて幸せな気持ちになれそう。ロゴのデザインが良い。などという薄っぺらい理由でとりあえずmockitoを使ってみようと思います。限界を感じたら JMockitも深耕してみようかなと。
+
+# 参考リンク
+
+- http://dontpanic.42.nl/2013/04/mockito-powermock-vs-jmockit.html
+- http://stackoverflow.com/a/6439134
+
+見つかった比較記事は両方とも JMockit 押し。ただし2013年とかちょっと古い。多分このネタ自体は既にhotではなくて、両者それなりに使われているのではないかと想像する次第。
+
+# バージョン番号など
+
+- mockito 1.10.19
+- JMockit 1.21
+- Java 8