たごもりすメモ

コードとかその他の話とか。

System.nanoTime() をJMockitで乗っ取る

Javaミドルウェアなど書いていると必然的に内部でタイマーを作ったりすることになってその制御に System.nanoTime() とかを使うことになると思うんだけど、じゃあテストどうしよっかと思うと、テストでは、まあ好きなタイミングで時間を進めたり好きなところで時間を止めたりってことが普通したいじゃないですか。どうすんのという話。

JMockitを使う

とりあえずJMockitを使っとけという話っぽい。static methodをモックできるのはこれしかない*1

JMockitは理想的なモックフレームワーク - じゅんいち☆かとうの技術日誌

ちょいちょい調べると例が見付かるので真似る。

import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;

import mockit.Expectations;
import mockit.Mocked;

public class FooTest
{
    @Test
    public void testTimer(@Mocked System unused)
    {
        long current = System.nanoTime();
        new Expectations() {{
            System.nanoTime(); result = current;
        }};
        assertThat(System.nanoTime(), is(current));
    }
}

JUnit4に怒られる

コンパイル通ったぜー、と思うとなんかテストでコケる。

java.lang.Exception: Method testTimer should have no parameters

調べてみたらこんなのが見付かった。要はJUnitよりも前にJMockitを書いておけと。えー、と思いながらとりあえず build.gradle を変更してみた。

dependencies {
    // jmockit should be written before junit
    // http://robertmarkbramprogrammer.blogspot.jp/2013/03/eclipse-and-jmockit-method-should-have.html
    testCompile group: 'org.jmockit', name: 'jmockit', version: '1.33'
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'

    // ...
}

そしたら期待通り動いてしまった。えー。ジャバむずかしいな。

(追記) なんか期待通りに動かない

複数のテストケースでそれぞれ違う値でモックしようとしたら結果がおかしくなる……みたいなのが多発してこれはやばいということになった。調べるとJITが走ったらダメになるみたいな話もあるし、ここで苦労するべきではないと判断。撤退。

*1:今でもそうなのかな? まあ、たぶんそう