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
をスローするので失敗するテストが必要です。次に、これを修正するためのコードを記述します。
私が気づいたのは、例外がテストメソッドにスローされないため、テストは常にパスすることです。
tl; dr
jDK8より前:古い良いtry
-catch
ブロックをお勧めします。
jDK8以降:AssertJまたはカスタムラムダを使用してexceptional動作をアサートします。
長い話
自分で自分で書くtry
-catch
ブロックまたはJUnitツール(@Test(expected = ...)
または_@Rule ExpectedException
_ JUnitルール機能)を使用できます。
しかし、これらの方法はそれほどエレガントではなく、他のツールと読みやすさをうまく組み合わせることはできません。
テストされた動作の周りにブロックを記述し、catchブロックにアサーションを記述するtry
-catch
ブロックは問題ないかもしれませんが、多くの場合、このスタイルはテストの読み取りフローを中断することがわかります。また、try
ブロックの最後に_Assert.fail
_を記述する必要があります。そうしないと、テストでアサーションの一方が欠落する可能性があります。 [〜#〜] pmd [〜#〜]、findbugsまたはソナーこのような問題を特定します。
@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
}
_
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 問題も参照してください。
したがって、これらの上記のオプションにはすべての注意事項があり、明らかにコーダーエラーの影響を受けません。
この回答を作成した後、有望に見えるプロジェクトに気づきました 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を使用することをお勧めします。これは、例外をアサートするだけではない場合があるためです
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");
}
_
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
は、不格好に感じてもブロックします。
私があなたを間違って理解していない場合は、次のようなものが必要です:
@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());
}
}
別のアプローチは、代わりに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コードをたくさん見ました。時間が許せば、頻繁にテストをレビューするのにお金がかかります。
通常、各テストケースは新しいインスタンスで実行されるため、インスタンス変数を設定しても効果はありません。そうでない場合は、 'throw
'変数を静的にします。