例外がスローされないことをテストする方法は?


238

それを行う1つの方法は、

@Test
public void foo(){
   try{
      //execute code that you expect not to throw Exceptions.
   }
   catch(Exception e){
      fail("Should not have thrown any exception");
   }
}

これを行うためのより明確な方法はありますか?(おそらくJUnitを使用してい@Ruleますか?)


10
JUnitテストは、予期された例外以外の例外をスローした場合、失敗したと判断されます。通常、例外は想定されていません。
Raedwald 2013

JUnitでの失敗とエラーの区別はありませんか?1つ目はテストが失敗したことを意味し、2つ目は予期しないことが起こったことを意味します。
Vituel 2014

回答:


198

あなたはこれに間違った方法で取り組んでいます。機能をテストするだけです。例外がスローされた場合、テストは自動的に失敗します。例外がスローされない場合、テストはすべて緑色になります。

この質問は時々興味を引くことに気づいたので、少し拡大します。

単体テストの背景

ユニットテストを行う場合は、作業の単位と見なすものを自分で定義することが重要です。基本的には、単一の機能を表す複数のメソッドまたはクラスを含む場合と含まない場合があるコードベースの抽出。

または、Art of Unit Testing、2nd Edition by Roy Osherove、11ページで定義されているように:

ユニットテストは、テストされている作業単位を呼び出し、そのユニットのシングルエンド結果に関するいくつかの仮定をチェックするコードを自動的に一枚です。ユニットテストは、ほとんどの場合、ユニットテストフレームワークを使用して記述されます。それは簡単に書くことができ、迅速に実行されます。信頼でき、読みやすく、保守可能です。量産コードが変更されていない限り、結果は一貫しています。

理解しておくべき重要なことは、1つの作業単位は通常1つの方法だけではなく、非常に基本的なレベルでは1つの方法であり、その後他の作業単位によってカプセル化されるということです。

ここに画像の説明を入力してください

理想的には、作業の個別の単位ごとにテストメソッドを用意して、どこで問題が発生しているのかを常に即座に確認できるようにする必要があります。この例では、getUserById()ユーザーを返す基本的なメソッドが呼び出され、合計3ユニットの作品があります。

最初の作業単位では、有効な入力と無効な入力の場合に、有効なユーザーが返されるかどうかをテストする必要があります。
データソースによってスローされている例外はすべてここで処理する必要があります。ユーザーが存在しない場合、ユーザーが見つからないときに例外がスローされることを示すテストが必要です。このサンプルはIllegalArgumentException@Test(expected = IllegalArgumentException.class)アノテーションでキャッチされたものである可能性があります。

この基本的な作業単位のすべてのユースケースを処理したら、レベルを上げます。ここでもまったく同じことを行いますが、現在のレベルのすぐ下のレベルからの例外のみを処理します。これにより、テストコードが適切に構造化され、どこにでも移動する必要がなく、アーキテクチャ全体をすばやく実行して、問題点を見つけることができます。

テストの有効で誤った入力の処理

この時点で、これらの例外をどのように処理するかが明確になっているはずです。入力には、有効な入力と誤った入力の2種類があります(入力は厳密には有効ですが、正しくありません)。

有効な入力で作業するときは、作成したテストが機能するという暗黙の期待値を設定します。

このようなメソッド呼び出しは次のようになりますexistingUserById_ShouldReturn_UserObject。このメソッドが失敗した場合(例:例外がスローされた場合)、問題が発生したことがわかり、掘り始めることができます。

誤った入力nonExistingUserById_ShouldThrow_IllegalArgumentExceptionを使用し、例外を予期する別のテスト()を追加することにより、メソッドが間違った入力で行うことになっていることをメソッドが実行するかどうかを確認できます。

TL; DR

テストで2つのことを実行しようとしました。有効な入力と誤った入力を確認してください。これを2つの方法に分割して、それぞれが1つのことを行うことで、テストがより明確になり、どこがうまくいかないのかについての概要が大幅に改善されます。

階層化された作業単位を覚えておくことで、階層の上位にあるレイヤーに必要なテストの量を減らすこともできます。これは、下位のレイヤーで問題が発生した可能性があるすべてのことを説明する必要がないためです。現在のレイヤーの下のレイヤーは、依存関係が機能するという事実上の保証であり、何か問題が発生した場合は、現在のレイヤーにあります(下位レイヤー自体がエラーをスローしない場合)。


2
問題は、私がTDDを試みており、使用しているコラボレーターの1人が例外をスローしていることです。そのため、コラボレーターによってスローされた例外を消費しているという事実をテストする必要があります
Ankit Dhingra 2013

6
あなたの機能は例外の処理に依存していると言っていますか?それはコードの匂いです。問題を上品にキャッチできるように例外があります。フロー制御には使用されません。例外をスローする必要があるシナリオをテストする場合は、expectedアノテーションを使用する必要があります。コードが失敗するシナリオをテストし、エラーが正しく処理されているかどうかを確認したい場合は、expectedアサートを使用して、解決されているかどうかを確認します。
Jeroen Vannevel 2013

コラボレーターで発生した例外から回復できず、log.debug( "Error message")を使用して問題をログに記録するだけです。したがって、私が主張できる可能性のあるcatchブロックの一部として発生する副作用はありません。
Ankit Dhingra 2013

5
@JeroenVannevel例外のスローを引き起こすエラー状況が適切に処理されることをテストすることは完全に有効です。
–ThorbjørnRavn Andersen 2013

1
@dpkはい、できます。throws IllegalArgumentExceptionテストに追加します。最後に必要なのは、例外がある場合、テストが赤くなることです。さて、何を推測しますか?書く必要はありませんfail()。@Jeroen Vannevelが書いたように:「例外がスローされると、テストは自動的に失敗します。」
Amedee Van Gasse、2016

132

SonarQubeのルール「squid:S2699」:「このテストケースに少なくとも1つのアサーションを追加する」ので、私はこれに遭遇しました。

私は単純なテストをしましたが、その唯一の目的は例外をスローせずに通過することでした。

この単純なコードを考えてみましょう:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

このメソッドをテストするためにどのようなアサーションを追加できますか?確かに、その周りで試行錯誤を行うことはできますが、それはコードの肥大化にすぎません。

ソリューションはJUnit自体から来ています。

例外がスローされず、この動作を明示的に説明する場合expectedは、次の例のように追加するだけです。

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class 期待値のデフォルトです。


30
これが最良の答えだと思います。受け入れられた答えは素晴らしいです、そして、作者はコードのにおいを指摘するのが正しいです。しかし、彼は特定の質問に実際には答えませんでした。
HellishHeat 2017

4
期待されるデフォルト値はNoneであることに注意してください。そのため、メソッドに@Testアノテーションを付けるだけで十分です。
oziomajnr


41

JUnit 5(Jupiter)には、例外の有無を確認するための3つの機能があります。

assertAll​()

アサートすることを全て供給executables
  例外をスローしません。

assertDoesNotThrow​()

アサートの実行
  供給をexecutable/ supplier
スローしませんあらゆる種類の例外を

  この関数は
  、JUnit 5.2.0(2018年4月29日)以降で使用できます。

assertThrows​()

アサート付属の実行がexecutable
スローの例外expectedType
  とリターンの例外を

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}

1
これが今の最良の答えです。他の回答では、JUnitの古いバージョンについて説明しています
Tejesh Raut

29

Java 8ではこれがはるかに簡単になり、Kotlin / Scalaでは二重になっています。

少しユーティリティクラスを書くことができます

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

そしてあなたのコードは単純になります:

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

Java-8にアクセスできない場合は、痛々しいほど古いJava機能を使用します。任意のコードブロックと簡単なコメントです。

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

そして最後に、kotlinを使用して、最近好きになった言語は次のとおりです。

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

あなたがこれをどのように表現したいかをいじくる余地はたくさんありますが、私は常に流暢な主張のファンでした。


について

あなたはこれに間違った方法で取り組んでいます。機能をテストするだけです。例外がスローされた場合、テストは自動的に失敗します。例外がスローされない場合、テストはすべて緑色になります。

これは原則として正しいですが、結論としては正しくありません。

Javaでは、制御フローの例外を許可しています。これはDouble.parseDouble、a NumberFormatExceptionPaths.getを介したAPIなどのJREランタイム自体によって行われますInvalidPathException

Double.ParseDouble多分正規表現、多分手書きのパーサー、または倍精度浮動小数点数の範囲を特定の何かに制限する他のドメインルールを埋め込むものを使用して、の数値文字列を検証するコンポーネントを作成した場合、これをテストする最善の方法成分?明らかなテストは、結果の文字列が解析されたときに例外がスローされないことを主張することだと思います。上記assertDoesNotThrowまたは/*comment*/{code}ブロックのいずれかを使用して、そのテストを記述します。何かのようなもの

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

私はまた、上でこのテストをパラメータ化することをお勧めしますinput使用してTheoriesまたはParameterizedあなたがより簡単に他の入力のために、このテストを再利用できるようにします。または、エキゾチックなものにしたい場合は、テスト生成ツール(およびこれ)を使用できます。TestNGはパラメーター化されたテストをよりよくサポートしています。

私は特に嫌見つけることは使用しての勧告で@Test(expectedException=IllegalArgumentException.class)この例外が危険なほど広いです。テスト対象のコンポーネントのコンストラクターのようにコードが変更されif(constructorArgument <= 0) throw IllegalArgumentException()、テストがその引数に0を提供していたのは便利であり、これは非常に一般的なことです。テストデータを適切に生成するのは驚くほど難しい問題です。何もテストしていなくても、緑色のバーになります。そのようなテストは役に立たないよりも悪いです。


2
(予期される例外の使用に関して)JUnit 4.13以降、を使用Assert.assertThrowsして、一部のコードが例外をスローすることを確認できます。
MageWind 2017年

22

コードのすべてのエラーをキャッチできるほど運が悪い場合。あなたは愚かにすることができます

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}

1
ほんの小さな提案ですが、テストする前にException exすべき= null;です。
デニース

4
これは優れたソリューションではありません。例外をスローしてはならないメソッドがスローすると、有用なエラーメッセージが表示されません。例外をスローしてはならないメソッドを呼び出し、その戻り値(または例外のロギングなどの副作用)をテストします。後で予期せずに例外がスローされると、テストは失敗します。
NamshubWriter 2015

3
または、Assert.fail()をキャッチに配置するだけで、より簡単できれいなIMOになります。
isaac.hazan 2015年

はい、あなたに賛成です。もう1つの方法は、メソッド@Test(expected = InvalidRequestException.class)の上に注釈を追加することです
Ben Tennyson

スペルの誤りは紛らわしいです:thisShouldThroughError- > thisShouldThrowError
Oscar Bravo


7

この投稿は現在6歳ですが、Junitの世界では多くの変化がありました。Junit5を使用すると、

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

例:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

Junit5の新しいバージョンを使用している人々の助けになることを願っています


ここに具体的な例外クラスを指定する方法があればいいのにと思います。私は、この内部をしなければならないAwaitility「S untilAsserted(ThrowingRunnable assertion)。テスト中のシステムは現在、提供するThrowingRunnableで特定の例外をスローしていますが、停止するまで少し時間がかかります。ただし、別の例外がスローされる場合は、テストがすぐに失敗するようにします。
Ubeogesh

1

テストターゲットが例外を消費するかどうかをテストする場合。テストは(jMock2を使用したコラボレーターのモック)のままにしておきます。

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

ターゲットがスローされた例外を消費する場合、テストはパスします。そうでない場合、テストは失敗します。

例外消費ロジックをテストする場合、状況はさらに複雑になります。消費をあざける可能性のある協力者に委託することをお勧めします。したがって、テストは次のようになります。

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

ただし、ログに記録したいだけの場合は、過剰に設計されていることがあります。この場合には、この記事(http://java.dzone.com/articles/monitoring-declarative-transachttp://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there -an-easy-way /)この場合、tddを要求した場合に役立つことがあります。


1

assertNull(...)を使用します

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}

6
これは誤解を招くと思います。catchブロックに到達することassertNullはないため、も実行されません。しかし、すぐに読者は、スローされないケースを本当に検証するアサーションが作成されたという印象を受けます。言い換えると、catchブロックに到達した場合、例外は常にnull以外であり、単純なで置き換えることができますfail
Andreas

確かに誤解を招く、.....しかし、待ってください...ああ、わかりました... ブロック内にはassertNull(e)記述eできないようnullに、テストが失敗したと報告しcatchます...マイクこれはただの奇妙なプログラミングです:-/。 ..はい、少なくともfail()アンドレアスが言うように使用
Julien

1

ルールを作成することで、例外がスローされないことを期待できます。

@Rule
public ExpectedException expectedException = ExpectedException.none();

ExpectedExceptionsは、スローされた例外を表明するために使用されます。提供するコードは、ルールを初期化するだけで、アサーションの要件を追加できます。このコード自体は、値をまったく追加しません。javadocは次のようにも述べています: "/ ** *例外がスローされないことを期待する{@linkplain TestRuleルール}を返します(このルールがない場合の動作と同じです)* /"したがって、それがない場合とまったく同じ結果になります。
Pim Hazebroek、2017年

私はあなたに同意し、そのようには使用しませんが、例外がスローされなかったと断言することは可能です。テストに合格した場合は、例外がスローされなかったと言うのに十分なはずですが、一方で質問がある場合は、その必要性があるはずです。そしてめったにないがそれでも時々それを目に見えるようにするのは良いです。コードと状況が変更され、特定のエッジケースに対するテストがない場合はどうなりますか?
LazerBanana 2017年

予想される例外を除いて、それをどのように主張するかを知りたいと思います。そして、はい、要件が変更され、特定のエッジケースに対するテストがない場合、ねじ込みます;-)常にすべてのコーナーケースをカバーします。
Pim Hazebroek、2017年

どういう意味ですか?あなたはそれを主張しないで、あなたはそれを期待します。この場合、例外はありません。あなたが何をしているのかわからない。
LazerBanana 2017年

0

これは最善の方法ではないかもしれませんが、テストされているコードブロックから例外がスローされないことを確実にします。

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}

0

これを行うには、@ Ruleを使用し、次に示すようにメソッドreportMissingExceptionWithMessageを呼び出します。これはScalaコードです。

ここに画像の説明を入力してください


1
private val?この言語はなんですか?明らかにJavaではありません; pそして、スクリーンショットとしてコードを提供しないでください、それは歓迎されません。
Andremoniy


気になる部分を削除しました。画像も入れ替えてみます。コードを追加する方法を考え出したていない、まだ...
Crenguta S

-1

次は、すべての例外のテストに失敗します。チェックありまたはチェックなしです。

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}

-1

junitからのアサーションに基づいて、任意の種類の独自のアサーションを作成できます。

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

そしてテスト:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

一般的に言えば、シナリオに関係なく、意味のある場所でテストを即座に失敗させる可能性があります( "bla bla bla")。たとえば、テストケースで何かがスローされた場合に失敗するように、try / catchブロックでそれを使用します。

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

これは私たちがテストするメソッドのサンプルです。特定の状況で失敗してはならないが、失敗する可能性があるメソッドがあると仮定します。

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

上記の方法は簡単なサンプルです。しかし、これは障害がそれほど明白ではない複雑な状況で機能します。インポートがあります:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;

カスタムコードの作成を伴わない、アサーションがスローされていないことを確認するかなり優れたオプションがあります。@Ruleはそのうちの一つです
Vargan

@Vargan特に、独自のアサーションを作成する目的で、JUnitによるデザインで独自のアサーションを作成する方法を指摘しました。JUnitは、設計により、特にその目的のために、独自のルールを作成するために、まだ実装されていないアサートを使用してJUnitの動作を拡張します。すべてがこの世界で実装されているわけではないため、これらのアサーションは、JUnitアサーションが成功または失敗、および失敗のレポートに関して機能するのと同じように機能します。
armagedescu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.