web-dev-qa-db-ja.com

例外がスローされなかったことを確認する方法

Mockitoを使用した単体テストで、NullPointerExceptionがスローされなかったことを確認したいと思います。

public void testNPENotThrown{
    Calling calling= Mock(Calling.class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
}

テストではtestClassを設定し、Callingオブジェクトとプロパティを設定して、メソッドがNullPointerExceptionをスローするようにしました。

I verify Calling.method()が呼び出されないこと。

public void testMethod(){
    if(throw) {
        throw new NullPointerException();
    }

    calling.method();
}

NullPointerExceptionをスローするので失敗するテストが必要です。次に、これを修正するためのコードを記述します。

私が気づいたのは、例外がテストメソッドにスローされないため、テストは常にパスすることです。

17
well_i

tl; dr

  • jDK8より前:古い良いtry-catchブロックをお勧めします。

  • jDK8以降:AssertJまたはカスタムラムダを使用してexceptional動作をアサートします。

長い話

自分で自分で書くtry-catchブロックまたはJUnitツール(@Test(expected = ...)または_@Rule ExpectedException_ JUnitルール機能)を使用できます。

しかし、これらの方法はそれほどエレガントではなく、他のツールと読みやすさをうまく組み合わせることはできません。

  1. テストされた動作の周りにブロックを記述し、catchブロックにアサーションを記述するtry-catchブロックは問題ないかもしれませんが、多くの場合、このスタイルはテストの読み取りフローを中断することがわかります。また、tryブロックの最後に_Assert.fail_を記述する必要があります。そうしないと、テストでアサーションの一方が欠落する可能性があります。 [〜#〜] pmd [〜#〜]findbugsまたはソナーこのような問題を特定します。

  2. @Test(expected = ...)機能は、少ないコードで記述でき、このテストを記述した場合にコーディングエラーが発生しにくいため、興味深いものです。 しかし、このアプローチにはいくつかの領域がありません。

    • 原因やメッセージなど、例外に関する追加の事項をテストでチェックする必要がある場合(適切な例外メッセージは非常に重要です。正確な例外タイプを用意するだけでは不十分な場合があります)。
    • また、メソッドに期待が置かれているため、テストされたコードの記述方法によっては、テストコードの間違った部分が例外をスローする可能性があり、誤検知テストが発生します [〜#〜] pmd [〜#〜]findbugsまたはSonarは、そのようなコードに関するヒントを提供します。

      _@Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      _
  3. ExpectedExceptionルールは、以前の警告を修正する試みでもありますが、期待スタイルを使用するため、使用するのは少し厄介に感じられます。EasyMockユーザーは、このスタイルをよく知っています。一部の人にとっては便利かもしれませんが、行動駆動型開発(BDD)またはアレンジアサートアサート(AAA)の原則に従うと、 ExpectedExceptionルールは、これらの記述スタイルに適合しません。それとは別に、期待する場所によっては、_@Test_と同じ問題が発生する可能性があります。

    _@Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    _

    予想される例外がテストステートメントの前に配置されていても、テストがBDDまたはAAAに従っている場合は、読み取りフローが中断されます。

    ExpectedExceptionの作者のJUnitに関するこの comment 問題も参照してください。

したがって、これらの上記のオプションにはすべての注意事項があり、明らかにコーダーエラーの影響を受けません。

  1. この回答を作成した後、有望に見えるプロジェクトに気づきました catch-exception です。

    プロジェクトの説明にあるように、コーダーはコードを流暢なコードで記述して例外をキャッチし、このアサーションを後でアサートできるようにします。また、 Hamcrest または AssertJ のような任意のアサーションライブラリを使用できます。

    ホームページから抜粋した簡単な例:

    _// given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    _

    コードが非常に簡単であることがわかるので、特定の行で例外をキャッチします。then APIは、AssertJ APIを使用するエイリアスです(assertThat(ex).hasNoCause()...を使用するのと同様)。 ある時点で、プロジェクトはAssertJの祖先であるFEST-Assertに依存していました。 編集:プロジェクトがJava 8 Lambdasサポートを作成しているようです。

    現在、このライブラリには2つの欠点があります。

    • この記事の執筆時点では、このライブラリはMockito 1.xに基づいていると言うことは注目に値します。テストされたオブジェクトのモックを舞台裏で作成するためです。 Mockitoはまだ更新されていないため、このライブラリは最終クラスまたは最終メソッドでは機能しません。また、現在のバージョンのmockito 2に基づいていたとしても、グローバルモックメーカー(_inline-mock-maker_)を宣言する必要があります。このモックメーカーには、通常のモックメーカーとは異なる欠点があるためです。

    • さらに別のテスト依存関係が必要です。

    ライブラリがラムダをサポートすると、これらの問題は当てはまりませんが、AssertJツールセットによって機能が複製されます。

    catch-exceptionツールを使用しない場合は、すべてを考慮して、少なくともJDK7までは、try-catchブロックの古い良い方法をお勧めします。また、JDK 8ユーザーは、AssertJを使用することをお勧めします。これは、例外をアサートするだけではない場合があるためです

  2. JDK8を使用すると、ラムダはテストシーンに入り、例外的な動作を主張する興味深い方法であることが証明されました。 AssertJが更新され、並外れた動作を表明するためのNice Fluent APIが提供されました。

    AssertJ を使用したサンプルテスト:

    _@Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    _
  3. JUnit 5のほぼ完全な書き直しにより、アサーションは 改善された になり、例外を適切にアサートするための独創的な方法として興味深いものになる可能性があります。しかし、実際にはアサーションAPIはまだ少し貧弱で、外側には何もありません assertThrows

    _@Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    _

    お気づきのとおり、assertEqualsは引き続きvoidを返しているため、AssertJのようなアサーションのチェーンは許可されていません。

    また、MatcherまたはAssertとの名前の衝突を覚えている場合は、Assertionsとの同じ衝突に備えてください。

今日(2017-03-03)AssertJの使いやすさ、発見可能なAPI、開発の迅速なペース、およびデファクトテスト依存性は、テストフレームワーク(JUnitかどうかに関係なく)に関係なくJDK8での最良のソリューションです。以前のJDKはtry-に依存する必要がありますcatchは、不格好に感じてもブロックします。

17
Brice

私があなたを間違って理解していない場合は、次のようなものが必要です:

@Test(expected = NullPointerException.class)
public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
    Assert.fail("No NPE");
}

しかし、テストの名前を "NPENotThrown"とすると、次のようなテストが期待されます。

public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();
    try {
        verify(calling, never()).method();
        Assert.assertTrue(Boolean.TRUE);
    } catch(NullPointerException ex) {
        Assert.fail(ex.getMessage());
    }
}
5
Stefan Beike

別のアプローチは、代わりにtry/catchを使用することです。それは少しだらしのないですが、私が理解していることから、このテストはTDDの場合と同じように短期間です:

@Test
public void testNPENotThrown{
  Calling calling= Mock(Calling.class);
  testClass.setInner(calling);
  testClass.setThrow(true);

  try{
    testClass.testMethod();
    fail("NPE not thrown");
  }catch (NullPointerException e){
    //expected behaviour
  }
}

編集:これを書いたとき、私は急いでいました。 「このテストはTDDのため、とにかく短命です」とは、このテストをすぐに修正するためのコードを書くつもりであり、将来NullPointerExceptionがスローされることはないということです。次に、テストを削除することもできます。結果として、美しいテストを書くのに多くの時間を費やす価値はおそらくありません(したがって、私の提案:-))

より一般的には:

(たとえば)メソッドの戻り値がnullではないことをアサートするテストから開始することは、確立されたTDDの原則であり、NullPointerException(NPE)をチェックすることは、これを回避する1つの可能な方法です。ただし、プロダクションコードには、NPEがスローされるフローがないと考えられます。あなたはヌルをチェックして、それから賢明なことをするつもりだと思います。 NPEがスローされていないことを確認するため、この時点でこの特定のテストは冗長になります。次に、nullが検出されたときに何が起こるかを検証するテストに置き換えることができます。たとえば、NullObjectを返すか、その他の種類の例外をスローします。

もちろん、冗長なテストを削除する必要はありませんが、削除しないと、ビルドが少し遅くなり、テストを読む各開発者が不思議に思うようになります。 「うーん、NPE?確かにこのコードはNPEをスローすることはできませんか?」テストクラスにこのような冗長なテストがたくさんあるTDDコードをたくさん見ました。時間が許せば、頻繁にテストをレビューするのにお金がかかります。

2
Mark Chorley

通常、各テストケースは新しいインスタンスで実行されるため、インスタンス変数を設定しても効果はありません。そうでない場合は、 'throw'変数を静的にします。

0
Juned Ahsan