タイトル通りです。コードから読み取ってください。
※タイトルが少し正しくなかったので直しました
サンプルコード
足し算をするロジックのテストをしたいと考えます。
package myapp.fixturesample; public class MyLogic { public static int add(int lhs, int rhs){ return lhs + rhs; } }
これに対するテストコードを書きます。Fixtureクラス分けろよって話もそうですが、とりあえず。
package myapp.fixturesample; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.*; import org.junit.Ignore; import org.junit.experimental.runners.Enclosed; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; @RunWith(Enclosed.class) public class MyLogicTest { @RunWith(Theories.class) public static class AddTest{ @DataPoints public static Fixture[] FIXTURES = { new Fixture(1, 1, 2), new Fixture(1, 2, 3), new Fixture(2, 1, 3), new Fixture(2, 2, 4), new Fixture(99, 99, 188), // oops }; @Theory public void addTest(Fixture f){ assertThat(MyLogic.add(f.lhs, f.rhs), is(f.expect)); } } @Ignore("its fixture class") public static class Fixture{ public final int lhs; public final int rhs; public final int expect; public Fixture(int lhs, int rhs, int expect) { this.lhs = lhs; this.rhs = rhs; this.expect = expect; } } }
問題点
失敗したときのスタックトレースが分かりにくい。
org.junit.experimental.theories.internal.ParameterizedAssertionError: addTest("myapp.fixturesample.MyLogicTest$Fixture@61e4705b" <from FIXTURES[4]>) 〜中略〜 Caused by: java.lang.AssertionError: Expected: is <188> but: was <198> at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8) at myapp.fixturesample.MyLogicTest$AddTest.addTest(MyLogicTest.java:27) 〜以下略〜
なるほど、なるほど、27行目が間違えて・・・
あ、これちがう、FIXTURESの4番目が間違えて・・・あ、5番目が間違えてたのかー。
ってなことを500個もある中から探すのは大変だよなァ!?アァン?
解決策
ちょっと汚いけどこうする。
package myapp.fixturesample; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.*; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Ignore; import org.junit.experimental.runners.Enclosed; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; @RunWith(Enclosed.class) public class MyLogicTest { @RunWith(Theories.class) public static class AddTest { @DataPoints public static Fixture[] FIXTURES = { new Fixture(1, 1, expect(2)), new Fixture(1, 2, expect(3)), new Fixture(2, 1, expect(3)), new Fixture(2, 2, expect(4)), new Fixture(99, 99, expect(188)), }; @Theory public void addTest(Fixture f) { assertThat(MyLogic.add(f.lhs, f.rhs), is(f.expect)); } public static Matcher<Integer> expect(final int value) { final StackTraceElement ste = Thread.currentThread().getStackTrace()[2]; // expect()を呼び出している箇所がとれる return new TypeSafeMatcher<Integer>() { @Override protected void describeMismatchSafely(Integer item, Description mismatchDescription) { super.describeMismatchSafely(item, mismatchDescription); mismatchDescription.appendText("\n"); mismatchDescription.appendText("== failed fixture == \n"); mismatchDescription.appendText("at ").appendText(ste.toString()).appendText("\n"); mismatchDescription.appendText("== original stacktrace =="); } @Override public void describeTo(Description description) { description.appendValue(value); } @Override protected boolean matchesSafely(Integer item) { return item != null && item.equals(value); } }; } } @Ignore("its fixture class") public static class Fixture { public final int lhs; public final int rhs; public final Matcher<Integer> expect; public Fixture(int lhs, int rhs, Matcher<Integer> expect) { this.lhs = lhs; this.rhs = rhs; this.expect = expect; } } }
やったことは以下。
- Fixtureに使ってる期待値をintからassertThatとかで使うMatcherを使うようにした
- 独自のMatcherを返すstatic methodを書いた
- 内部でスタックトレースからメソッドを呼び出した元の情報を取って、Matcherで失敗したときに表示するようにして返してる。
そうすると、失敗時のスタックトレースはこうなる
org.junit.experimental.theories.internal.ParameterizedAssertionError: addTest("myapp.fixturesample.MyLogicTest$Fixture@1376c05c" <from FIXTURES[4]>) 〜中略〜 Caused by: java.lang.AssertionError: Expected: is <188> but: was <198> == failed fixture == at myapp.fixturesample.MyLogicTest$AddTest.<clinit>(MyLogicTest.java:26) == original stacktrace == at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8) at myapp.fixturesample.MyLogicTest$AddTest.addTest(MyLogicTest.java:31) 〜以下略〜
見てもらいたいのはここ
== failed fixture ==
at myapp.fixturesample.MyLogicTest$AddTest.(MyLogicTest.java:26)
何が嬉しいのかというと、Eclipse上でJUnit走らせたときに結果が出てくるウィンドウからコードジャンプが出来て失敗した行に1発で飛べて便利なんですねえ。
(該当行の左にスタックトレースのマークが出てないけど。仕事で使ってた時は出た気がするんだけどなー、よくわからん。)
終わり
これに気づいたとき、俺天才かって思った。
けど、今時じゃない感じがするし、多分今のテストの書き方も良くないんだと思う。
どういう値を指定した結果どうなったのか、とかわからないし・・・いやコードジャンプすれば一発ですよ・・・?あ、eclipseなんて知らない、そうですか・・・