Assertを使用して例外がスローされたことを確認するにはどうすればよいですか?


830

Assert例外がスローされたことを確認するために(または他のTestクラスを使用して)どうすればよいですか?


どの単体テストフレームワークを使用していますか?
Kevin Pullin、

3
Visual Studio Integrated
Alex

4
ExpectedException属性は役に立ちませんか?参照:msdn.microsoft.com/en-us/library/…–
shahkalpesh

2
おかしい、私はこれに対する答えを探し終えたところ、stackoverflow.com/questions/741029/testing-exceptionsでそれを見つけました。
dfjacobs 2009年

回答:


978

"Visual Studio Team Test"の場合、ExpectedException属性をテストのメソッドに適用しているようです。

ここのドキュメントのサンプル:Visual Studio Team Testを使用した単体テストのチュートリアル

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}

25
上記のExpectedException属性はNUnitでも機能します(ただし、[TestMethod]は[Test]である必要があります)。
dbkk 2009年

5
@dbkk:NUnitでもまったく同じように機能しません-メッセージは、例外メッセージをマット化する必要がある文字列として扱われます(そしてIUはそれがより理にかなっていると思います)
Ruben Bartelink

29
この属性は仕事を完了し、c#プログラマーの組み込み機能ですが、柔軟性が十分でないため、この属性の使用はお勧めしません。例外タイプがテストセットアップコードによってスローされた場合に何が起こるかを検討してください。または、例外オブジェクトの状態をテストする場合はどうでしょうか。通常、メッセージ全体をテストするのではなく、StringAssert.Contains(e.Message ...)を使用します。他の回答で説明されているように、assertメソッドを使用します。
スティーブ

3
NUnit 3.0では削除されるため、NUnitでExpectedExceptionを使用しないでください。Assert.Throws <SpecificException>()を使用したい
Terence

5
MsTest内でAssert.ThrowsException <T>およびAssert.ThrowsExceptionAsync <T>を使用できます。
ゴパルクリシュナン2017

257

通常、テストフレームワークはこれに対する答えを持っています。しかし、十分に柔軟でない場合は、いつでもこれを行うことができます。

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

@Jonasが指摘するように、これは基本例外のキャッチには機能しません。

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

どうしても例外をキャッチする必要がある場合は、Assert.Fail()を再スローする必要があります。しかし、実際には、これは手書きではいけない兆候です。テストフレームワークでオプションを確認するか、より意味のある例外をスローしてテストできるかどうかを確認します。

catch (AssertionException) { throw; }

キャッチする例外の種類の指定を含め、このアプローチを好きなように適用できるはずです。特定のタイプのみが必要な場合は、次のようにcatchブロックを終了します。

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}

20
+1、例外のタイプを超えてアサーションを作成する必要がある場合は、属性の代わりにこの方法を使用します。たとえば、例外インスタンスの特定のフィールドが特定の値に設定されていることを確認する必要がある場合はどうでしょうか。
Pavel Repin

2
エラーメッセージを指定する必要はありません。これで十分です:[ExpectedException(typeof(ArgumentException))]
mibollma '20 / 07/20

5
この解決策が最良だと思います。[ExpectedException(typeof(ArgumentException))]は、テストが単純である場合に使用しますが、私の見解では、遅延ソリューションであり、問​​題を解決するのが難しい場合があります。このソリューションを使用すると、より正確なテストを実行するための具体的な制御が可能になります。さらに、テスト実行レポートへの書き込みラインをテストして、例外が実際に期待どおりにスローされたことを確認できます。
evilfish 2013年

12
Assert.Fail()は例外を発生させるので注意してください。キャッチすると、テストに合格します。
ジョナス

4
@ Vinnyq12つまり、上の例の最初のテストは決して失敗しないということです。例外がスローされた場合(およびExpectedExceptionAttributeによって「キャッチ」されなかった場合)、テストは失敗します
Jonas

113

これを実装するための私の好ましい方法は、Throwsと呼ばれるメソッドを作成し、他のAssertメソッドと同じように使用することです。残念ながら、.NETでは静的拡張メソッドを作成できないため、このメソッドをAssertクラスのビルドに実際に属しているかのように使用することはできません。MyAssertと呼ばれる別のものまたは類似のものを作成するだけです。クラスは次のようになります。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        public static void Throws<T>( Action func ) where T : Exception
        {
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }

            if ( !exceptionThrown )
            {
                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
                    );
            }
        }
    }
}

つまり、単体テストは次のようになります。

[TestMethod()]
public void ExceptionTest()
{
    String testStr = null;
    MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}

これは、他の単体テスト構文と非常によく似た動作をします。


1
よりコンパクトな実装のために、ブールフラグを削除し、呼び出しの直後にスローを配置します。
2013年

11
これを改善する唯一のことは、例外の属性などが正しいことをアサートし続けることができるように、関数がキャッチされた例外を返すようにすることです。
Mark Hildreth、2013年

2
ありがとう!これは、1つのメソッドで複数の例外をテストする簡単な方法であるため、私にとって最良のアプローチのようです。また、読みやすくなります。
David Sherret、2014

2
@MickeyPerlstein属性は、テストのAAAルールに違反します。具体的には、Actに到達する前にArrangeが例外をスローした場合、テストは成功します。
freedomn-m 2016

2
MicrosoftはついにMSTestの更新に取り掛かった-v2のサポートAssert.ThrowsException<T>-blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…をAssert.ThrowsExceptionAsync<T>参照
Quango

62

NUNITを使用する場合、次のようなことができます。

Assert.Throws<ExpectedException>(() => methodToTest());


さらに検証するために、スローされた例外を保存することもできます。

ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);

参照:http : //nunit.org/docs/2.5/exceptionAsserts.html


60

最初はExpectedException属性を持っていなかったMSTestを使用している場合、これを行うことができます。

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}

3
これは機能しますが、ロジックが非常に複雑であるため、一般的にはお勧めしません。複雑だと言っているわけではありませんが、このコードブロックを複数のテスト(10秒、100秒のテスト)用に書くかどうか検討してください。このロジックは、適切に設計されたアサートメソッドに変換する必要があります。他の回答を参照してください。
スティーブ・

35

ExpectedExceptionの使用には注意が必要です。ここに示すように、いくつかの落とし穴につながる可能性があります。

http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx

そしてここ:

http://xunit.github.io/docs/comparisons.html

例外をテストする必要がある場合は、方法に眉をひそめることが少なくなります。try {act / fail} catch {assert}メソッドを使用できます。これは、ExpectedException以外の例外テストを直接サポートしていないフレームワークに役立ちます。

より良い代替手段は、他のすべての間違いから学び、改善された、非常に近代的で将来を見据えた拡張可能な単体テストフレームワークであるxUnit.NETを使用することです。そのような改善の1つはAssert.Throwsで、これは例外をアサートするためのはるかに優れた構文を提供します。

あなたはgithubのでxUnit.NETを見つけることができます。http://xunit.github.io/


4
NUnit 2.5はAssert.Throwsスタイルの構文もサポートすることに注意してください-nunit.com/index.php?p=releaseNotes&r=2.5
Alconja

ユニットテストがExpectedExceptionの使用時に例外を通知するために停止する方法は、私を狂わせます。なぜMSは、自動化されたテストに手動のステップがあることが良い考えだと思ったのですか?リンクをありがとう。
Ant、

@Ant:MSがNUnitをコピーしたので、本当の質問は、なぜNUnitはそれが良いアイデアだと思ったのですか?
jrista

28

MSTest(v2)には、次のように使用できるAssert.ThrowsException関数があります。

Assert.ThrowsException<System.FormatException>(() =>
            {
                Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
            }); 

nugetでインストールできます: Install-Package MSTest.TestFramework


2018では、テスト中のユニットのみがスローされ、他のコードはチェックされないため、これはベストプラクティスと見なされています。
CM

24

私が取り組んでいるプロジェクトでは、これを行う別のソリューションがあります。

まず、例外の原因となったメソッド呼び出しを考慮に入れるため、ExpectedExceptionAttributeは好きではありません。

代わりにヘルパーメソッドを使用してこれを行います。

テスト

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

HelperMethod

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

きちんと、そうではありません;)


14

これはテストメソッドの属性です...アサートは使用しません。このように見えます:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()

13

次を使用してNugetからパッケージをダウンロードできます。PM > Assert.Throws()を追加するInstall-Package MSTestExtensions MSTESTにNUnitの/ xUnitフレームのスタイルで構文を。

高レベルの手順:アセンブリをダウンロードしてBaseTestから継承すると、Assert.Throws()を使用できます構文をます。

Throws実装のメインメソッドは次のようになります。

public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
    try
    {
        task();
    }
    catch (Exception ex)
    {
        AssertExceptionType<T>(ex);
        AssertExceptionMessage(ex, expectedMessage, options);
        return;
    }

    if (typeof(T).Equals(new Exception().GetType()))
    {
        Assert.Fail("Expected exception but no exception was thrown.");
    }
    else
    {
        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
    }
}

開示:このパッケージをまとめました。

詳細:http : //www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html


例をありがとう。Assert.DoesNotThrow()または同等のものをテストする方法の例はありますか?
Lane Goolsby

10

簡単な1行でこれを実現できます。

操作foo.bar()が非同期の場合:

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());

foo.bar()非同期でない場合

Assert.ThrowsException<Exception>(() => foo.bar());

1
他にもたくさんの答えがあります。私にとっては、例外の種類のみで既知の障害状態をテストする簡単な方法を探していました。これにより、最も読みやすいテストケースが作成されます。注:例外タイプは、標準のtry-catchなどの継承された例外クラスとは一致しないため、上記の例はArgumentExceptionforをトラップしません。テストする高度な基準がある場合は、古いTry Catchを使用して例外応答をテストすることをお勧めしますが、多くの場合、これは非常に役立ちます。
Chris Schaller

5

ExpectedException属性の使用(制約が多すぎてエラーが発生しやすいため)や、各テストでtry / catchブロックを作成すること(複雑すぎてエラーが発生しやすいため)はお勧めしません。適切に設計されたアサートメソッドを使用します-テストフレームワークによって提供されるか、独自に作成します。これが私が書いて使用したものです。

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

使用例:

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

ノート

検証コールバックをサポートする代わりに例外を返すことは、そうすることでこのアサートの呼び出し構文が、私が使用する他のアサートと大きく異なることを除いて、理にかなったアイデアです。

他とは異なり、呼び出しから例外が伝播するかどうかのみをテストできるため、「スロー」ではなく「伝播」を使用します。例外がスローされることを直接テストすることはできません。しかし、私はあなたがスローをイメージすることができると思います:スローされ、キャッチされません。

最終的な思考

この種のアプローチに切り替える前に、テストで例外タイプのみを検証する場合はExpectedException属性を使用し、さらに検証が必要な場合はtry / catchブロックを使用することを検討しました。しかし、各テストにどの手法を使用するかを考える必要があるだけでなく、ニーズの変化に応じてコードを1つの手法から別の手法に変更するのは簡単なことではありませんでした。1つの一貫したアプローチを使用すると、精神的な労力を節約できます。

したがって、要約すると、このアプローチは、使いやすさ、柔軟性、堅牢性(間違いを犯しにくい)を備えています。


4

上記の@Richibanによって提供されるヘルパーは、例外がスローされる状況を処理しないが、期待されるタイプを処理しないことを除いて、うまく機能します。次のことについて説明します。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        /// <summary>
        /// Helper for Asserting that a function throws an exception of a particular type.
        /// </summary>
        public static void Throws<T>( Action func ) where T : Exception
        {
            Exception exceptionOther = null;
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }
            catch (Exception e) {
                exceptionOther = e;
            }

            if ( !exceptionThrown )
            {
                if (exceptionOther != null) {
                    throw new AssertFailedException(
                        String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                        exceptionOther
                        );
                }

                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                    );
            }
        }
    }
}

2
うーん...私はアイデアを理解していますが、それがより良いことに同意するかどうかはわかりません。特定の例外が発生することを保証したいからといって、他のすべてがアサーションエラーとしてラップされる必要があるという意味ではありません。未知の例外の場合、他のアサート操作と同様に、スタックをバブルアップする必要があります。
Crono 2013年

@Martin exceptionOtherを含むコードを削除し、2番目のcatch句から単純に再スローします
Tom Lint

4

他のテストクラスの使用について言及しているので、ExpectedException属性よりも優れたオプションは、ShoudlyShould.Throwを使用することです

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

注文を作成するには、顧客住所を持っている必要があるとしましょう。そうでない場合、メソッドの結果はになります。次に、次のように書きます。CreateOrderForCustomerArgumentException

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

これは、 ExpectedExceptionエラーをスローする対象を特定しているため属性ています。これにより、テストの要件が明確になり、テストが失敗した場合の診断も容易になります。

Should.ThrowAsync非同期メソッドのテストもあることに注意してください。


4

別の方法として、実際にテストの次の2行で例外がスローされることをテストしてみることができます。

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);

4

VSの組み込みユニットテストで、「任意の例外」がスローされたことを確認したいだけで、タイプがわからない場合は、次のすべてを使用できます。

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}

3

まあ私はここで他の皆が以前に言ったことをかなり要約します...とにかく、ここに私が良い答えに従って作成したコードがあります:)すべてはコピーと使用だけです...

/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
    try
    {
        methodToExecute();
    }
    catch (TException) {
        return;
    }  
    catch (System.Exception ex)
    {
        Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
    }
    Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
}


2

これは、使用しているテストフレームワークに依存しますか?

たとえば、MbUnitでは、属性を使用して予期される例外を指定し、本当に予期される例外を確実に取得できます。

[ExpectedException(typeof(ArgumentException))]

2

NUnitを使用する場合は、次のことを試してください。

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));

2

NFluentと呼ばれる素晴らしいライブラリがあり、アサーションの記述高速化し、簡単にします

例外をスローするためのアサーションを記述するのは非常に簡単です。

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }

1

これは古い質問ですが、ディスカッションに新しい考えを付け加えたいと思います。Arrange、Act、AssertパターンをExpected、Arrange、Act、Assertに拡張しました。予期される例外ポインタを作成し、それが割り当てられていることを表明できます。これは、assertsをcatchブロックで実行するよりもクリーンに感じられ、Actセクションは主にコードの1行だけでテスト中のメソッドを呼び出すことができます。また、コード内の複数のポイントに出入りするAssert.Fail();必要もありませんreturn。その他の例外がスローされると、テストが失敗します。これは、テストがキャッチされないためです。また、予期したタイプの例外がスローされたが、それが予期したものではなかった場合、メッセージまたは他のプロパティに対するアサート例外は、テストが不注意でパスしないようにするのに役立ちます。

[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
    // Expectations
    InvalidOperationException expectedException = null;
    string expectedExceptionMessage = "Bar did something invalid.";

    // Arrange
    IDependency dependency = DependencyMocks.Create();
    Foo foo = new Foo(dependency);

    // Act
    try
    {
        foo.Bar();
    }
    catch (InvalidOperationException ex)
    {
        expectedException = ex;
    }

    // Assert
    Assert.IsNotNull(expectedException);
    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.