例外がスローされることを確認するためにAssertで例外をテストする最良の方法


97

これは例外をテストするための良い方法だと思いますか?助言がありますか?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);

MSテストを使用しています。

回答:


137

私はいくつかの異なるパターンを使用しています。ExpectedExceptionほとんどの場合、例外が予想されるときにこの属性を使用します。ほとんどの場合これで十分ですが、これで十分でない場合もあります。例外はキャッチできない可能性があります-リフレクションによって呼び出されるメソッドによってスローされるため-または、トランザクションがロールバックされているか、値がまだ設定されているなど、他の条件が保持されていることを確認したいだけかもしれません。これらのケースtry/catchでは、正確な例外を期待するブロックにラップしAssert.Fail、コードが成功した場合はを実行し、一般的な例外をキャッチして、別の例外がスローされないようにします。

最初のケース:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
     var obj = new ClassRequiringNonNullParameter( null );
}

2番目のケース:

[TestMethod]
public void MethodTest()
{
    try
    {
        var obj = new ClassRequiringNonNullParameter( null );
        Assert.Fail("An exception should have been thrown");
    }
    catch (ArgumentNullException ae)
    {
        Assert.AreEqual( "Parameter cannot be null or empty.", ae.Message );
    }
    catch (Exception e)
    {
        Assert.Fail(
             string.Format( "Unexpected exception of type {0} caught: {1}",
                            e.GetType(), e.Message )
        );
    }
}

16
多くの単体テストフレームワークは、アサーションエラーを例外として実装しています。したがって、2番目のケースのAssert.Fail()は、catch(Exception)ブロックによってキャッチされ、例外メッセージが非表示になります。catch(NUnit.Framework.AssertionException){throw;}または同様のものを追加する必要があります-私の回答を参照してください。
GrahamS 2009

@Graham-私はこれを頭の上から打ちました。通常は、タイプに加えて例外メッセージも出力します。重要なのは、2番目のハンドラーがアサーションの失敗をキャッチし、エラーに関する情報で「再失敗」するため、テストが失敗することです。
tvanfosson 2009

1
コードは機能的には健全ですが、ExpectedException属性の使用(制約が多すぎてエラーが発生しやすいため)や、各テストでtry / catchブロックを作成すること(複雑すぎてエラーが発生しやすいため)はお勧めしません。適切に設計されたassertメソッドを使用します-テストフレームワークによって提供されるか、独自のメソッドを記述します。あなたはより良いコードを達成することができ、異なるテクニックを選択したり、テストが変化したときに、テクニックを変更したりする必要はありません。stackoverflow.com/a/25084462/2166177を
steve

FYI-私Assert.Throwsはこれらの両方のケースをカバーする強く型付けされたメソッドを持つxUnitの使用に移りました 。
tvanfosson

ExpectedException属性は、例外がスローされるかどうかをテストするための厄介で日付のある方法です。以下の私の完全な回答を参照してください。
bytedev

41

2017年、新しいMSTest V2フレームワークを使用して、より簡単に行うことができます。

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);

これは、System.Exceptionがスローされた場合にのみ成功します。それ以外はSystem.ArgumentException、テストに失敗します。
sschoof

2
別のタイプの例外が予想される場合は、それをテストする必要があります...この例では、次のことを行う必要があります。Assert.ThrowsException <ArgumentException>(()=> myClass.MyMethodWithError());
Icaro Bombonato

2
注意すべき重要な点は、の使用はAssert.ThrowsException<MyException>、提供された例外タイプに対してのみテストを行うものであり、派生した例外タイプに対してはテストしないことです。私の例では、試験された場合Subであった(基本クラスから派生型)、テストはだろう失敗しますThrowMyInheritedExceptionMyException
Ama

テストを拡張し、例外タイプとその派生タイプを受け入れる場合は、を使用しTry { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}ます。Catch (AssertFailedException e) {throw;}(algeekからのコメントを参照)の最も重要性に注意してください
Ama

17

私はここに新しく、コメントしたり、反対票を投じるという評判はありませんが、アンディホワイトの返信の例の欠陥を指摘したいと思います。

try
{
    SomethingThatCausesAnException();
    Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
    // whatever logging code
}

私が精通しているすべての単体テストフレームワークではAssert.Fail、例外をスローすることで機能するため、汎用キャッチは実際にテストの失敗を隠蔽します。SomethingThatCausesAnException()がスローされない場合、Assert.Fail意志はありますが、失敗を示すためにテストランナーにバブルアウトすることはありません。

予期される例外をキャッチする必要がある場合(つまり、例外のメッセージ/プロパティなどの特定の詳細をアサートする場合)、基本のExceptionクラスではなく、特定の予期されるタイプをキャッチすることが重要です。これにより、Assert.Fail例外が発生します(ユニットテストフレームワークが行うのと同じタイプの例外をスローしていない場合)、SomethingThatCausesAnException()メソッドによってスローされた例外の検証は可能です。


15

v 2.5以降、NUnitには、Assert例外をテストするための次のメソッドレベルのがあります。

Assert.Throws。正確な例外タイプをテストします。

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());

そしてAssert.Catch、指定されたタイプの例外、またはこのタイプから派生した例外タイプをテストします。

Assert.Catch<Exception>(() => someNullObject.ToString());

余談ですが、例外をスローする単体テストをデバッグするときは、VS が例外でブレークしないようにする必要があります

編集する

ただ、一般的なのリターンは、以下のマシューさんのコメントの例を与える Assert.Throwsと、Assert.Catchあなたはその後、さらに検査のために調べることができる例外の種類を持つ例外です。

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);

2
Roy Osheroveは、Art of Unit Testing、第2版、セクション2.6.2でこれを推奨しています。
Avi 2014

2
私のようなAssert.Throwsあなたが例外自体にさらなるアサーションを書くことができるように加えて、それは例外を返します。
マシュー

問題はNUnitではなくMSTestに対するものでした。
bytedev 2017

@nashwan OPの元の質問にはその資格はなく、タグ付けはまだMS-Testの資格を備えていません。現状では、C#、. Net、ユニットテストの質問です。
StuartLC 2017

11

残念ながら、MSTestは実際にはExpectedException属性しか持っていません(MSがMSTestを気にかけている程度を示しているだけです)。これは、IMOがArrange / Act / Assertパターンを壊し、例外を予期するコード行を正確に指定できないためです。発生します。

MSTestを使用するために(クライアントによって/ forced)使用しているときは、常にこのヘルパークラスを使用します。

public static class AssertException
{
    public static void Throws<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }

    public static void Throws<TException>(Action action, string expectedMessage) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            Assert.AreEqual(expectedMessage, ex.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + ex.Message + "' was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }
}

使用例:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));

10

ExpectedException属性を使用する代わりに、テストクラスに役立つ2つのメソッドを定義することがあります。

AssertThrowsException() デリゲートを受け取り、予想されるメッセージを伴う予想される例外をスローすることを表明します。

AssertDoesNotThrowException() 同じデリゲートを受け取り、例外をスローしないことを表明します。

このペアリングは、例外がスローされたが、他のケースではスローされないことをテストする場合に非常に役立ちます。

それらを使用すると、私の単体テストコードは次のようになります。

ExceptionThrower callStartOp = delegate(){ testObj.StartOperation(); };

// Check exception is thrown correctly...
AssertThrowsException(callStartOp, typeof(InvalidOperationException), "StartOperation() called when not ready.");

testObj.Ready = true;

// Check exception is now not thrown...
AssertDoesNotThrowException(callStartOp);

素敵で端正なの?

my AssertThrowsException()およびAssertDoesNotThrowException()メソッドは、次のように共通の基本クラスで定義されています。

protected delegate void ExceptionThrower();

/// <summary>
/// Asserts that calling a method results in an exception of the stated type with the stated message.
/// </summary>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
/// <param name="expectedExceptionType">The expected type of the exception, e.g. typeof(FormatException).</param>
/// <param name="expectedExceptionMessage">The expected exception message (or fragment of the whole message)</param>
protected void AssertThrowsException(ExceptionThrower exceptionThrowingFunc, Type expectedExceptionType, string expectedExceptionMessage)
{
    try
    {
        exceptionThrowingFunc();
        Assert.Fail("Call did not raise any exception, but one was expected.");
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(expectedExceptionType, ex, "Exception raised was not the expected type.");
        Assert.IsTrue(ex.Message.Contains(expectedExceptionMessage), "Exception raised did not contain expected message. Expected=\"" + expectedExceptionMessage + "\", got \"" + ex.Message + "\"");
    }
}

/// <summary>
/// Asserts that calling a method does not throw an exception.
/// </summary>
/// <remarks>
/// This is typically only used in conjunction with <see cref="AssertThrowsException"/>. (e.g. once you have tested that an ExceptionThrower
/// method throws an exception then your test may fix the cause of the exception and then call this to make sure it is now fixed).
/// </remarks>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
protected void AssertDoesNotThrowException(ExceptionThrower exceptionThrowingFunc)
{
    try
    {
        exceptionThrowingFunc();
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow any NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.Fail("Call raised an unexpected exception: " + ex.Message);
    }
}

4

ExpectedExceptionAttributeでテストをマークします(これはNUnitまたはMSTestの用語です。他のユニットテストフレームワークのユーザーは翻訳する必要がある場合があります)。


ExpectedExceptionAttributeを使用しないでください(理由は以下の私の投稿に記載されています)。NUnitにはAssert.Throws <YourException>()があり、MSTestでは以下のAssertExceptionクラスのようなものを使用します。
bytedev 2017

3

ほとんどの.netユニットテストフレームワークでは、テストメソッドに[ExpectedException]属性を設定できます。ただし、これでは、予期した時点で例外が発生したことがわかりません。ここでxunit.netが役立ちます。

xunitにはAssert.Throwsがあるので、次のようなことができます。

    [Fact]
    public void CantDecrementBasketLineQuantityBelowZero()
    {
        var o = new Basket();
        var p = new Product {Id = 1, NetPrice = 23.45m};
        o.AddProduct(p, 1);
        Assert.Throws<BusinessException>(() => o.SetProductQuantity(p, -3));
    }

[Fact]は[TestMethod]に相当するxunitです


MSTestを使用する必要がある場合(私はしばしば雇用者に強制されます)、以下の私の回答を参照してください。
bytedev

0

NUnitのクリーンなデリゲート構文の使用を提案します。

テストの例ArgumentNullExeption

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.