読者です 読者をやめる 読者になる 読者になる

600000007

プログラムと折り紙のブログです。

やっぱりassertThatは良いよねという話

追記 2016/6/12

テスト結果のメッセージを読みやすくしたいだけなら、assertThatにreasonを書く方が楽ですね。

stackoverflow.com

    @Test
    public void test() {
        File file = new File("/tmp/hoge");
        assertThat(file.getAbsoluteFile() + " should exist", file.exists(), is(equalTo(true)));
    }
java.lang.AssertionError: /tmp/hoge should exist
Expected: is <true>
     but: was <false>

追記終わり

assertThatやっぱ便利ですねー、という話です。

assertEqualsは何のテストに失敗しているのか分かりにくかった

public class AssertEqualTests {

	@Test
	public void test() {
		final File test1 = new File("/tmp/test1.txt");
		final File test2 = new File("/tmp/test2.txt");
		final File test3 = new File("/tmp/test3.txt");
		assertEquals(true, test1.exists());
		assertEquals(true, test2.exists());
		assertEquals(true, test3.exists());
	}

}

ファイルが存在しているかどうかをテストするだけです。
で、これを実行してみると、テストが失敗しました。

java.lang.AssertionError: expected:<true> but was:<false>

これだとどのファイルで失敗したか分からないですね。
まあスタックとレースをちゃんと見れば何行目のassertEqualsで失敗しているか分かるので判定はできるのですが、あまり直感的ではありません。

Matcherを作って原因を分かりやすくしよう!

そこでassertThatとhamcrestの出番です。
もっとわかりやすいメッセージを返してくれるMatcherを作ってみました。

public class FileExist extends TypeSafeMatcher<File> {
 
    private final boolean isEexpectedResult;
    private File inspected;
 
    public FileExist(boolean exceptedResult) {
        this.isEexpectedResult = exceptedResult;
    }
 
    @Override
    public void describeTo(Description description) {
        description.appendText(inspected.getAbsolutePath());
        if (isEexpectedResult) {
            description.appendText(" exist");
        } else {
            description.appendText(" does not exist");
        }
 
    }
 
    @Override
    protected void describeMismatchSafely(File item, Description mismatchDescription) {
        mismatchDescription.appendText(inspected.getName());
        if (isEexpectedResult) {
            mismatchDescription.appendText(" does not exist.");
        } else {
            mismatchDescription.appendText(" exist.");
        }
 
    }
 
    @Override
    protected boolean matchesSafely(File item) {
        this.inspected = item;
        return item.exists() == isEexpectedResult;
    }
 
    @Factory
    public static <T> Matcher<File> exist() {
        return new FileExist(true);
    }
 
    @Factory
    public static <T> Matcher<File> NotExist() {
        return new FileExist(false);
    }
 
}

TypeSafeMatcherを継承して独自Matcherを作っています。
ファイルを受け取って期待通り存在しているか、もしくは存在していないかをテストできます。
そしてdescribeToで成功時のメッセージを、describeMismatchSafelyで失敗時のメッセージを作成しています。

テストは以下のようになります。

public class AssertThatTests {

	@Test
	public void test() {
		final File test1 = new File("/tmp/test1.txt");
		final File test2 = new File("/tmp/test2.txt");
		final File test3 = new File("/tmp/test3.txt");
		assertThat(test1,exist());
		assertThat(test2,exist());
		assertThat(test3,exist());
	}

}

そして実行結果。

java.lang.AssertionError: 
Expected: /tmp/test2.txt exist
     but: test2.txt does not exist.

test2.txtが存在していないというのが一目でわかりますねー。素敵です。
Matcherを簡単に作れるというのは、結果の分かりやすくする事にも役立つんですね。

ちょっとはまったこと。

Matcherを作るとき、どうしてもdescribeMismatchSafelyが見つからず@Overrideがエラーを吐いてしまいました。
原因は我らがstackoverflowで見つかりました。

  • junitライブラリ内部の古いhamcrestを参照してるんじゃない?今はhamcrestは別ライブラリだよ
  • Junitを4.10以上にして、junit.jarとhamcrest-core.jarをビルドパスに加えてみ
  • hamcrest-core.jarはjunit.jarより上位にしてね