web-dev-qa-db-ja.com

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

その方法の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を使っていますか?)

174
Ankit Dhingra

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

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

単体テストの背景

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

あるいは、 Roy Osherove著の単体テストの芸術、第2版 、11ページに定義されているように、

単体テストは、テスト対象の作業単位を呼び出して、その単位の単一の最終結果に関するいくつかの前提を確認する自動化されたコードです。単体テストはほとんどの場合単体テストフレームワークを使用して書かれています。それは簡単に書くことができて速く走ります。それは信頼でき、読みやすく、そして保守可能です。本番コードが変更されていない限り、結果は一貫しています。

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

enter image description here

理想的には、各作業単位ごとにテスト方法を用意して、問題が発生している場所をいつでもすぐに確認できるようにする必要があります。この例では、ユーザーを返すgetUserById()という基本メソッドがあり、合計3つの作業単位があります。

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

この基本的な作業単位に対するすべてのユースケースを処理したら、レベルを上げます。ここでもまったく同じですが、現在のレベルのすぐ下のレベルから来る例外のみを処理します。これはあなたのテストコードをよく構造化された状態に保ちそしてあなたが物事がうまくいかないところを見つけるためにアーキテクチャーを素早く調べてどこにでも飛び回らなくてもいいようにします。

テストの有効かつ誤った入力を処理する

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

validの入力を扱うときは、どんなテストを書いてもうまくいくという暗黙の期待を設定しています。

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

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

TL、DR

テストで2つのことをしようとしていました。有効な入力と誤った入力を確認します。これを2つの方法に分けることで、それぞれが1つのことを実行し、より明確なテストと、問題が発生した場所の概要がわかりやすくなります。

階層化された作業単位を念頭に置くことによって、下位層で問題が発生した可能性があることすべてを考慮する必要がないため、階層の上位層に必要なテストの量を減らすこともできます。現在の階層より下の階層はあなたの依存関係が機能するという事実上の保証です。そして何かがうまくいかなかった場合、それはあなたの現在の階層にあります(下位の階層がそれ自体エラーを投げないと仮定します)。

167
Jeroen Vannevel

私はSonarQubeのルール "squid:S2699"のためにこれにつまずいた。

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

この単純なコードを見てください。

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は、期待値のデフォルトです。

59
Sven Döring

Java 8ではこれがずっと簡単になり、Kotlin/Scalaでは2倍になりました。

ちょっとしたユーティリティクラスを書くことができます

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は制御の流れに例外を認めています。これは、JREランタイム自体によって、NumberFormatExceptionを介したDouble.parseDoubleおよびInvalidPathExceptionを介したPaths.getのようなAPIで行われます。

Double.ParseDoubleの数値文字列を検証するコンポーネントを作成したとしましょう。Regex、手書きのパーサー、あるいはdoubleの範囲を特定のものに限定する他のドメインルールを組み込んだものなど、テストするのが最善です。このコンポーネント?明らかなテストは、結果の文字列が解析されても例外がスローされないことを表明することだと思います。このテストは、上記の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でこのテストをパラメータ化することをお勧めします。あるいは、もしあなたがエキゾチックに行きたいのなら、 テスト生成ツール (そして this )を選ぶことができます。 TestNGはパラメータ化されたテストをよりよくサポートします。

私が特に嫌いなのは、@Test(expectedException=IllegalArgumentException.class)の使用を推奨することです。この例外は危険なほど広い。テスト中のコンポーネントのコンストラクタがif(constructorArgument <= 0) throw IllegalArgumentException()を持つようにコードが変更されていて、それが便利だったためにその引数に0を指定していた場合 - そしてこれは非常に一般的です。何もテストしなくてもtestはgreen-barになります。そのようなテストは無用より悪いです。

29
Groostav

With AssertJネイティブアサーション3.7.

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
16
denu

あなたがあなたのコードのすべてのエラーを捕らえるのに十分不運であるならば。愚かにもできる

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

JUnit 5 (Jupiter)は例外の有無をチェックする3つの関数を提供します。

assertAll​()

アサート that all指定executables
例外をスローしません。

assertDoesNotThrow​()

アサートその実行
executable/supplierが提供されました
投げないあらゆる種類の 例外

この機能は利用可能です
--- JUnit 5.2. (2018年4月29日)。

assertThrows​()

Asserts与えられたexecutableの実行
throwsexpectedTypeの例外
そして 例外 を返します。

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));
    }

}
10
olibre

JUnit5はこの目的のためにassertAll()メソッドを追加しています。

assertAll( () -> foo() )

出典:JUnit 5 API

5
razalghul

これを行うには、@ Ruleを使用してから、次に示すようにメソッドreportMissingExceptionWithMessageを呼び出します。これはScalaですが、Javaでも同様に簡単に実行できます。

enter image description here

2
Crenguta S

assertNull(...) を使う

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

テスト対象が例外を消費しているかどうかをテストしたい場合。テストを(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/)この場合にtddを主張すると20/testing-aspect-pointcutsは簡単な方法です/ )が役に立つかもしれません。

1
Yugang Zhou

ルールを作成しても、例外はスローされません。

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

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

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
MLS

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");

一般的に言えば、どんなシナリオでも、それが理にかなっているどんな場所でも、テストを即座に失敗する可能性があります。たとえば、テストケースで何かがスローされた場合に失敗するには、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.*;
0
armagedescu