web-dev-qa-db-ja.com

JavaでHamcrestを使用して例外をテストする方法は?

Hamcrestを使用して例外をテストするにはどうすればよいですか? https://code.google.com/p/hamcrest/wiki/Tutorial のコメントによると、「予期された属性を使用して、Junit 4によって例外処理が提供されます。」

だから私はこれを試してみましたが、うまくいったことがわかりました:

_public class MyObjectifyUtilTest {

    @Test
    public void shouldFindFieldByName() throws MyObjectifyNoSuchFieldException {
        String fieldName = "status";
        String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName);
        assertThat(field, equalTo(fieldName));
    }

    @Test(expected=MyObjectifyNoSuchFieldException.class)
    public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException {
        String fieldName = "someMissingField";
        String field = MyObjectifyUtil.getField(DownloadTask.class, fieldName);
        assertThat(field, equalTo(fieldName));      
    }

}
_

Hamcrestは、JUnitからの@Test(expected=...)アノテーション以上の追加機能を提供しますか?

誰かがこれについてGroovyで質問しましたが( Hamcrestを使用して例外をテストする方法は? )、私の質問はJavaで書かれた単体テストに関するものです。

13
Michael Osofsky

本当にHamcrestライブラリを使用する必要がありますか?

そうでない場合は、例外テストのJunitのサポートを使用して行う方法を次に示します。 ExpectedException クラスには、スローされたExceptionのタイプをチェックする以外に、目的に応じて使用できる多くのメソッドがあります。

これと組み合わせてHamcrestマッチャーを使用して特定の何かをアサートできますが、スローされた例外をJunitに期待させる方が良いです。

public class MyObjectifyUtilTest {

    // create a rule for an exception grabber that you can use across 
    // the methods in this test class
    @Rule
    public ExpectedException exceptionGrabber = ExpectedException.none();

    @Test
    public void shouldThrowExceptionBecauseFieldDoesNotExist() throws MyObjectifyNoSuchFieldException {
        String fieldName = "someMissingField";

        // a method capable of throwing MyObjectifyNoSuchFieldException too
        doSomething();

        // assuming the MyObjectifyUtil.getField would throw the exception, 
        // I'm expecting an exception to be thrown just before that method call
        exceptionGrabber.expect(MyObjectifyNoSuchFieldException.class);
        MyObjectifyUtil.getField(DownloadTask.class, fieldName);

        ...
    }

}

このアプローチは

  • @Test (expected=...)アプローチは、@Test (expected=...)がメソッドの実行が特定の例外をスローすることによって停止したかどうかをテストするだけであり、例外をスローしたい呼び出しがテストした場合ではないためです。たとえば、doSomethingメソッドがMyObjectifyNoSuchFieldException例外をスローしてもテストは成功しますが、これは望ましくない場合があります

  • スローされる例外のタイプだけでなく、それ以上のテストを行うことができます。たとえば、特定の例外インスタンスや例外メッセージなどを確認できます

  • 読みやすさと簡潔さのため、try/catchブロックアプローチ。

27
mystarrocks

アサーションエラーの説明を数えると、いい方法で実装できませんでした(おそらく、これがHamcrestがそのような機能を提供していない理由です)、Java 8でうまくプレイしている場合は、このようなものが必要になる可能性があります(ただし、以下で説明する問題のため、これが使用されることはないと思います)。

IThrowingRunnable

このインターフェイスは、例外をスローする可能性のあるコードをラップするために使用されます。 _Callable<E>_も使用できますが、後者は値を返す必要があるため、実行可能(「void-callable」)の方が便利だと思います。

_@FunctionalInterface
public interface IThrowingRunnable<E extends Throwable> {

    void run()
            throws E;

}
_

FailsWithMatcher

このクラスは、特定のコールバックが例外をスローすることを要求するマッチャーを実装します。この実装の欠点は、予期しない例外をスローする(またはシングルをスローしない場合でも)コールバックを使用しても、何が問題であるかを説明せず、エラーメッセージが完全に不明瞭になることです。

_public final class FailsWithMatcher<EX extends Throwable>
        extends TypeSafeMatcher<IThrowingRunnable<EX>> {

    private final Matcher<? super EX> matcher;

    private FailsWithMatcher(final Matcher<? super EX> matcher) {
        this.matcher = matcher;
    }

    public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType) {
        return new FailsWithMatcher<>(instanceOf(throwableType));
    }

    public static <EX extends Throwable> Matcher<IThrowingRunnable<EX>> failsWith(final Class<EX> throwableType, final Matcher<? super EX> throwableMatcher) {
        return new FailsWithMatcher<>(allOf(instanceOf(throwableType), throwableMatcher));
    }

    @Override
    protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
        try {
            runnable.run();
            return false;
        } catch ( final Throwable ex ) {
            return matcher.matches(ex);
        }
    }

    @Override
    public void describeTo(final Description description) {
        description.appendText("fails with ").appendDescriptionOf(matcher);
    }

}
_

ExceptionMessageMatcher

これは、スローされた例外メッセージを簡単にチェックするサンプルマッチャーです。

_public final class ExceptionMessageMatcher<EX extends Throwable>
        extends TypeSafeMatcher<EX> {

    private final Matcher<? super String> matcher;

    private ExceptionMessageMatcher(final Matcher<String> matcher) {
        this.matcher = matcher;
    }

    public static <EX extends Throwable> Matcher<EX> exceptionMessage(final String message) {
        return new ExceptionMessageMatcher<>(is(message));
    }

    @Override
    protected boolean matchesSafely(final EX ex) {
        return matcher.matches(ex.getMessage());
    }

    @Override
    public void describeTo(final Description description) {
        description.appendDescriptionOf(matcher);
    }

}
_

そしてテストサンプル自体

_@Test
public void test() {
    assertThat(() -> emptyList().get(0), failsWith(IndexOutOfBoundsException.class, exceptionMessage("Index: 0")));
    assertThat(() -> emptyList().set(0, null), failsWith(UnsupportedOperationException.class));
}
_

このアプローチに注意してください:

  • ...テストランナーに依存しない
  • ... 1つのテストで複数のアサーションを指定できます

そして最悪の場合、典型的な失敗は次のようになります

_Java.lang.AssertionError:  
Expected: fails with (an instance of Java.lang.IndexOutOfBoundsException and is "Index: 0001")  
     but: was <foo.bar.baz.FailsWithMatcherTest$$Lambda$1/127618319@6b143ee9>
_

多分assertThat()メソッドのカスタム実装を使用することで修正できます。

7

私は最もきれいな方法は次のような関数を定義することだと思います

public static Throwable exceptionOf(Callable<?> callable) {
    try {
        callable.call();
        return null;
    } catch (Throwable t) {
        return t;
    }
}

どこかにそして次にコール

assertThat(exceptionOf(() -> callSomethingThatShouldThrow()),
    instanceOf(TheExpectedException.class));

おそらく この答え のExceptionMessageMatcherのようなものも使用しています。

6

上記に加えて。

インターフェイスを... extends Exceptionに変更すると、次のようなエラーがスローされます。

@Override
protected boolean matchesSafely(final IThrowingRunnable<EX> runnable) {
    try {
        runnable.run();
        throw new Error("Did not throw Exception");
    } catch (final Exception ex) {
        return matcher.matches(ex);
    }
}

トレースは次のようになります。

Java.lang.Error: Did not throw Exception
    at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.Java:31)
    at de.test.test.FailsWithMatcher.matchesSafely(FailsWithMatcher.Java:1)
    at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.Java:65)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.Java:12)
    at org.junit.Assert.assertThat(Assert.Java:956)
    at org.junit.Assert.assertThat(Assert.Java:923)
    at 
    ...
0
Wolf