it-swarm.com.de

Wie verwende ich Assert, um zu überprüfen, ob eine Ausnahme ausgelöst wurde?

Wie verwende ich Assert (oder eine andere Testklasse?), Um zu überprüfen, ob eine Ausnahme ausgelöst wurde?

715
Alex

Für "Visual Studio Team Test" wird angezeigt, dass Sie das ExpectedException-Attribut auf die Testmethode anwenden.

Beispiel aus der Dokumentation hier: Ein Test zum Testen von Einheiten mit 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]");
}
854
Kevin Pullin

Normalerweise hat Ihr Test-Framework eine Antwort darauf. Wenn es nicht flexibel genug ist, können Sie dies immer tun:

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

@Jonas weist darauf hin, dass dies NICHT zum Einfangen einer Basis funktioniert. Ausnahme:

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

Wenn Sie Exception unbedingt abfangen müssen, müssen Sie Assert.Fail () erneut werfen. Aber wirklich, dies ist ein Zeichen, dass Sie dies nicht von Hand schreiben sollten; Überprüfen Sie Ihr Test-Framework auf Optionen oder prüfen Sie, ob Sie eine aussagekräftigere Ausnahme zum Testen auslösen können.

catch (AssertionException) { throw; }

Sie sollten in der Lage sein, diesen Ansatz an das anzupassen, was Sie möchten, einschließlich der Angabe, welche Ausnahmen zu erfassen sind. Wenn Sie nur bestimmte Typen erwarten, beenden Sie die catch-Blöcke mit:

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

Meine bevorzugte Methode, dies zu implementieren, besteht darin, eine Methode namens Throws zu schreiben und sie wie jede andere Assert-Methode zu verwenden. Leider können Sie in .NET keine statische Erweiterungsmethode schreiben. Daher können Sie diese Methode nicht so verwenden, als gehörte sie tatsächlich zur build in Assert-Klasse. mach einfach einen anderen namens MyAssert oder ähnliches. Die Klasse sieht so aus:

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

Das bedeutet, dass Ihr Gerätetest so aussieht:

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

Das sieht aus und verhält sich viel mehr wie der Rest Ihrer Unit-Testsyntax.

103
Richiban

Wenn Sie MSTest verwenden, das ursprünglich kein ExpectedException-Attribut hatte, können Sie Folgendes tun:

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

wenn Sie NUNIT verwenden, können Sie Folgendes tun:

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


Es ist auch möglich, die ausgelöste Ausnahme zu speichern, um sie weiter zu überprüfen

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

Siehe: http://nunit.org/docs/2.5/exceptionAsserts.html

44
damir

Seien Sie vorsichtig bei der Verwendung von ExpectedException, da dies zu mehreren Fallstricken führen kann, wie hier gezeigt:

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

Und hier:

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

Wenn Sie Ausnahmen testen müssen, gibt es weniger verpönte Möglichkeiten. Sie können die try {act/fail} catch {assert} -Methode verwenden, die für Frameworks nützlich sein kann, die keine direkte Unterstützung für Ausnahmetests mit Ausnahme von ExpectedException bieten. 

Eine bessere Alternative ist die Verwendung von xUnit.NET, einem sehr modernen, zukunftsweisenden und erweiterbaren Gerätetest-Framework, das aus allen anderen Fehlern gelernt und verbessert wurde. Eine solche Verbesserung ist Assert.Throws, die eine wesentlich bessere Syntax für das Durchsetzen von Ausnahmen bietet. 

Sie finden xUnit.NET unter github: http://xunit.github.io/

35
jrista

In einem Projekt, an dem ich arbeite, haben wir eine andere Lösung.

Zuerst mag ich das ExpectedExceptionAttribute nicht, weil es berücksichtigt, welcher Methodenaufruf die Ausnahme verursacht hat.

Ich mache das stattdessen mit einer Hilfsmethode.

Prüfung

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

Ordentlich, nicht wahr;)

24
Glenn

Es ist ein Attribut für die Testmethode ... Sie verwenden Assert nicht. Sieht aus wie das:

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

Sie können ein Paket von Nuget herunterladen: PM> Install-Package MSTestExtensions, das die Syntax Assert.Throws () im Stil von nUnit/xUnit zu MsTest hinzufügt.

Anweisungen auf hoher Ebene: Laden Sie die Assembly herunter und erben Sie sie von BaseTest. Sie können die Syntax Assert.Throws () verwenden.

Die Hauptmethode für die Throws-Implementierung sieht folgendermaßen aus:

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

Offenlegung: Ich habe dieses Paket zusammengestellt.

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

13

MSTest (v2) verfügt jetzt über eine Assert.ThrowsException-Funktion, die folgendermaßen verwendet werden kann:

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

Sie können es mit Nuget installieren: Install-Package MSTest.TestFramework

12
Martin Beeby

Sie können dies mit einer einfachen einzeiligen Lösung erreichen.

Wenn Ihre Operation foo.bar() asynchron ist:

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

Wenn foo.bar() nicht asynchron ist

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

Ich empfehle nicht, das ExpectedException-Attribut zu verwenden (da es zu einschränkend und fehleranfällig ist) oder in jedem Test einen try/catch-Block zu schreiben (da es zu kompliziert und fehleranfällig ist). Verwenden Sie eine gut entwickelte Assert-Methode - entweder in Ihrem Test-Framework oder schreiben Sie Ihre eigene. Hier ist, was ich geschrieben und verwendet habe. 

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

Beispiel verwendet:

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

ANMERKUNGEN

Die Ausnahme zurückzugeben, anstatt einen Validierungs-Callback zu unterstützen, ist eine vernünftige Idee, mit der Ausnahme, dass sich die aufrufende Syntax dieser Zusicherung von den anderen von mir verwendeten Zusicherungen unterscheidet.

Im Gegensatz zu anderen verwende ich "propagates" anstelle von "throws", da wir nur testen können, ob eine Ausnahme von einem Aufruf propagiert wird. Wir können nicht direkt testen, dass eine Ausnahme ausgelöst wird. Aber ich nehme an, Sie könnten Imagewürfe bedeuten: geworfen und nicht erwischt.

LETZTER GEDANKE

Bevor ich zu dieser Art von Ansatz wechselte, überlegte ich, das ExpectedException-Attribut zu verwenden, wenn ein Test nur den Ausnahmetyp verifizierte und ein try/catch-Block verwendet wurde, wenn eine weitere Überprüfung erforderlich war. Aber ich musste nicht nur darüber nachdenken, welche Technik für jeden Test verwendet werden sollte, sondern es war keine triviale Anstrengung, den Code von einer Technik zur anderen zu ändern. Ein einheitlicher Ansatz spart geistige Anstrengung. 

Zusammenfassend lässt sich sagen, dass dieser Ansatz sportlich ist: Benutzerfreundlichkeit, Flexibilität und Robustheit (schwer falsch zu machen).

5
steve

Der von @Richiban bereitgestellte Helfer funktioniert hervorragend, außer dass er die Situation nicht behandelt, in der eine Ausnahme ausgelöst wird, aber nicht der erwartete Typ. Die folgenden Adressen sind:

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

Als Alternative können Sie versuchen, Tests zu testen, die tatsächlich mit den nächsten 2 Zeilen in Ihrem Test ausgelöst werden.

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

Wenn Sie bei VS-integrierten Unit-Tests einfach überprüfen möchten, dass "eine Ausnahme" ausgelöst wird, der Typ jedoch nicht bekannt ist, können Sie alle Catch-Optionen verwenden:

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

Nun, ich werde so ziemlich zusammenfassen, was alle anderen hier vorher gesagt haben ... Wie auch immer, hier ist der Code, den ich entsprechend den guten Antworten gebaut habe :).

/// <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

Da Sie andere Testklassen erwähnen, ist die Verwendung von Shoudly 's Should.Throw eine bessere Option als das ExpectedException-Attribut.

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

Nehmen wir an, wir haben die Voraussetzung, dass customer eine address haben muss, um eine order zu erstellen. Wenn nicht, sollte die CreateOrderForCustomer-Methode eine ArgumentException ergeben. Dann könnten wir schreiben:

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

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

Dies ist besser als die Verwendung eines ExpectedException-Attributs, da wir genau festlegen, was den Fehler auslösen soll. Dies macht die Anforderungen in unseren Tests klarer und erleichtert auch die Diagnose, wenn der Test fehlschlägt.

Beachten Sie, dass es auch einen Should.ThrowAsync für das Testen asynchroner Methoden gibt.

3
Justin J Stark

Wenn Sie NUnit verwenden, versuchen Sie Folgendes:

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

Dies hängt davon ab, welches Test-Framework Sie verwenden.

In MbUnit können Sie beispielsweise die erwartete Ausnahme mit einem Attribut angeben, um sicherzustellen, dass Sie die Ausnahme erhalten, die Sie wirklich erwarten.

[ExpectedException(typeof(ArgumentException))]
2
Jay

Check out nUnit Docs für Beispiele zu: 

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

Obwohl dies eine alte Frage ist, möchte ich der Diskussion einen neuen Gedanken hinzufügen. Ich habe das zu erwartende Arrangement-, Act-, Assert-Muster, Arrange-, Act-, Assert-Muster erweitert. Sie können einen erwarteten Ausnahmezeiger erstellen und dann behaupten, dass er zugewiesen wurde. Dies fühlt sich sauberer an, als wenn Sie Ihre Asserts in einem catch-Block ausführen. Der Act-Abschnitt bleibt meist nur für die eine Codezeile, um die zu testende Methode aufzurufen. Sie müssen auch nicht Assert.Fail(); oder return von mehreren Stellen im Code aus. Jede andere ausgelöste Ausnahme führt dazu, dass der Test fehlschlägt, da er nicht abgefangen wird und wenn eine Ausnahme des erwarteten Typs ausgelöst wird, die jedoch nicht die erwartete war Die Ausnahmehilfe sorgt dafür, dass Ihr Test nicht unbeabsichtigt bestanden wird.

[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

Es gibt eine großartige Bibliothek namens NFluent , die beschleunigt und die Art und Weise erleichtert, wie Sie Ihre Assertions schreiben.

Es ist ziemlich einfach, eine Behauptung für das Auslösen einer Ausnahme zu schreiben:

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