web-dev-qa-db-ja.com

Assertを使って例外がスローされたことを確認する方法

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

741
Alex

"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, "[email protected]");
}
877
Kevin Pullin

通常あなたのテストフレームワークはこれに対する答えを持つでしょう。しかし、それが十分に柔軟でないならば、あなたはいつもこれをすることができます:

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
}

絶対にExceptionをキャッチする必要がある場合は、Assert.Fail()を再スローする必要があります。しかし、本当に、これはあなたがこれを手書きするべきではないというサインです。テストフレームワークでオプションを確認するか、テストするためにより意味のある例外をスローできるかどうかを確認してください。

catch (AssertionException) { throw; }

どのような種類の例外をキャッチするかを指定するなど、この方法を自分の好きなものに適応させることができます。特定の型しか期待できない場合は、catchブロックを次のようにして完成させます。

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

これを実装するための私の好ましい方法は、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());
}

これは、他の単体テスト構文と同じように見え、動作します。

105
Richiban

MSTestを使用している場合は、もともとExpectedException属性がありませんでしたが、これを行うことができます。

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

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

47
damir

ここに示されているようにいくつかの落とし穴をもたらす可能性があるのでExpectedExceptionの使用には用心してください。

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

そしてここ:

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

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

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

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

35
jrista

私が取り組んでいるプロジェクトでは、これを行う別の解決策があります。

最初に、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;
    }

きちんとした、それではない;)

24
Glenn

MSTest(v2)にはAssert.ThrowsException関数が追加されました。これは次のように使用できます。

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

あなたはnugetでそれをインストールすることができます:Install-Package MSTest.TestFramework

16
Martin Beeby

それはテストメソッドの属性です...あなたはAssertを使いません。こんな感じです:

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

次のようにしてNugetからパッケージをダウンロードできます。 PM> Install-Package MSTestExtensions MsTestにnUnit/xUnitのスタイルのAssert.Throws()構文を追加します。

高度な手順:アセンブリをダウンロードして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

13

これは簡単な一行で実現できます。

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

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

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

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

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

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

ノート

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

他のものとは異なり、例外が呼び出しから伝播するかどうかだけをテストできるので、 'throws'の代わりに 'propagates'を使用します。例外がスローされたことを直接テストすることはできません。しかし、私はあなたが投げるとキャッチしないことを意味するために投げをイメージすることができると思います。

決勝戦

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

要約すると、このアプローチは、使いやすさ、柔軟性、および堅牢性(間違って実行するのは困難です)を意味します。

5
steve

代わりに、テストを試すことができますが、実際にはテストの次の2行で例外がスローされます。

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

上記の@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))
                    );
            }
        }
    }
}
4
Martin Connell

VS組み込みの単体テストで、単に "any exception"がスローされていることを確認したいがタイプがわからない場合は、catch allを使用できます。

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

まあ、私はここで他のみんなが以前に言ったことをほとんど要約するつもりです...とにかく、これは私が良い答えに従って構築したコードです:)すべてはやるべきことはコピーして使うことです...

/// <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.");  
}
3
roeiba

他のテストクラスの使用について言及しているので、ExpectedException属性よりも良い選択肢は ShoudlyShould.Throw を使うことです。

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

order を作成するには、 customer address が必要であるという要件があるとします。そうでなければ、CreateOrderForCustomerメソッドはArgumentExceptionになります。それから私達は書くことができる:

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

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

これはExpectedException属性を使用するよりも優れています。なぜなら、エラーを発生させるべきものについて具体的に説明しているからです。これにより、テストの要件が明確になり、テストが失敗したときの診断も容易になります。

非同期メソッドのテスト用のShould.ThrowAsyncもあります。

3
Justin J Stark

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

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

これは、どのテストフレームワークを使用しているかによって異なりますか。

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

[ExpectedException(typeof(ArgumentException))]
2
Jay

以下の例については、 nUnit Docs をチェックしてください。

[ExpectedException( typeof( ArgumentException ) )]
2
Jon Masters

これは古い質問ですが、私は議論に新しい考えを加えたいと思います。 Arrange、Act、AssertパターンをExpected、Arrange、Act、Assertに拡張しました。期待される例外ポインタを作り、それが割り当てられたことを表明することができます。これは、キャッチブロックでAssertを実行するよりもクリーンに感じられます。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);
}
1
Adam Venezia

NFluent と呼ばれる素晴らしいライブラリがあり、これはを高速化し、アサーションの記述方法を容易にします

例外をスローするためのアサーションを書くのは非常に簡単です:

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