Frage Der beste Weg, Ausnahmen mit Assert zu testen, um sicherzustellen, dass sie ausgelöst werden


Denken Sie, dass dies eine gute Möglichkeit ist, Ausnahmen zu testen? Irgendwelche Vorschläge?

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

Assert.IsNotNull(exception);

Ich benutze MS Test.


76
2018-04-12 00:12


Ursprung


Antworten:


Ich habe ein paar verschiedene Muster, die ich benutze. Ich verwende das ExpectedException Attribut die meiste Zeit, wenn eine Ausnahme erwartet wird. Dies reicht in den meisten Fällen aus, es gibt jedoch Fälle, in denen dies nicht ausreicht. Die Ausnahme ist möglicherweise nicht abfragbar - da sie von einer Methode ausgelöst wird, die durch Reflektion aufgerufen wird - oder ich möchte nur überprüfen, ob andere Bedingungen erfüllt sind. Beispielsweise wird eine Transaktion zurückgesetzt oder ein Wert wurde noch festgelegt. In diesen Fällen wickle ich es in ein try/catch Block, der die genaue Ausnahme erwartet, tut ein Assert.Fail Wenn der Code erfolgreich ist, werden auch generische Ausnahmen abgefangen, um sicherzustellen, dass keine andere Ausnahme ausgelöst wird.

Erster Fall:

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

Zweiter Fall:

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

118
2018-04-12 00:40



Ich bin neu hier und habe nicht den Ruf zu kommentieren oder downvote, sondern wollte auf einen Fehler im Beispiel hinweisen Andy Whites Antwort:

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

In allen Unit-Test-Frameworks kenne ich Assert.Fail funktioniert, indem eine Ausnahme ausgelöst wird, sodass der generische Catch den Fehler des Tests maskiert. Ob SomethingThatCausesAnException() wirft nicht, die Assert.Fail wird, aber das wird dem Testläufer nie zum Erfolg kommen.

Wenn Sie die erwartete Ausnahme abfangen müssen (d. H. Um bestimmte Details wie die Nachricht / Eigenschaften der Ausnahme zu bestätigen), ist es wichtig, den bestimmten erwarteten Typ und nicht die Basis-Exception-Klasse abzufangen. Das würde erlauben das Assert.Fail Ausnahme zu bubble out (vorausgesetzt, Sie werfen nicht den gleichen Typ von Ausnahme wie das Unit-Test-Framework), erlauben aber weiterhin die Validierung der Ausnahme, die von Ihrem System ausgelöst wurde SomethingThatCausesAnException() Methode.


16
2018-04-12 19:01



Jetzt, 2017, können Sie es einfacher mit dem neuen tun MSTest V2 Framework:

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

12
2018-05-07 00:19



Ab v 2.5, NUnit hat die folgende Methodenebene Asserts zum Testen von Ausnahmen:

Assert.Throws, die auf einen genauen Ausnahmetyp prüft:

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

Und Assert.Catch, die auf eine Ausnahme eines bestimmten Typs oder einen von diesem Typ abgeleiteten Ausnahmetyp prüft:

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

Nebenbei, wenn Sie Unit-Tests testen, die Exceptions auslösen, möchten Sie vielleicht verhindern, dass VS von Brechen auf die Ausnahme.

Bearbeiten

Um nur ein Beispiel für den Kommentar von Matthew zu geben, die Rückkehr des Generikums Assert.Throws und Assert.Catch ist die Ausnahme mit dem Typ der Ausnahme, die Sie dann zur weiteren Prüfung prüfen können:

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

11
2017-10-25 11:33



Leider hat MSTest STILL nur das ExpectedException-Attribut (zeigt nur an, wieviel MS sich für MSTest interessiert), was IMO ziemlich scheußlich ist, weil es das Arrange / Act / Assert-Muster unterbricht und es nicht erlaubt, genau festzulegen, welche Codezeile die Ausnahme erwartet auftreten auf.

Wenn ich MSTest benutze (/ von einem Client erzwungen) verwende ich immer diese Hilfsklasse:

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

Anwendungsbeispiel:

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

10
2017-10-03 14:10



Als Alternative zur Verwendung ExpectedExceptionAttribut, ich definiere manchmal zwei hilfreiche Methoden für meine Testklassen:

AssertThrowsException() nimmt einen Delegaten und behauptet, dass es die erwartete Ausnahme mit der erwarteten Nachricht auslöst.

AssertDoesNotThrowException() nimmt den gleichen Delegaten und behauptet, dass es keine Ausnahme auslöst.

Diese Paarung kann sehr nützlich sein, wenn Sie testen möchten, ob eine Ausnahme in einem Fall ausgelöst wird, aber nicht in der anderen.

Mit ihnen könnte mein Unit Test Code wie folgt aussehen:

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

Nett und ordentlich huh?

Meine AssertThrowsException() und AssertDoesNotThrowException() Methoden werden auf einer gemeinsamen Basisklasse wie folgt definiert:

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

9
2018-04-12 18:51



Markieren Sie den Test mit dem ExpectedExceptionAttribute (dies ist der Begriff in NUnit oder MSTest; Benutzer anderer Einheitentestframeworks müssen möglicherweise übersetzt werden).


4
2018-04-12 00:17



Bei den meisten .net-Unit-Test-Frameworks können Sie ein [ExpectedException] -Attribut für die Testmethode angeben. Das kann Ihnen jedoch nicht sagen, dass die Ausnahme zu dem Zeitpunkt passiert ist, an dem Sie es erwartet haben. Das ist wo xunit.de kann helfen.

Mit xunit hast du Assert.Throws, also kannst du so etwas machen:

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

[Fakt] ist das xunit-Äquivalent von [TestMethod]


2
2018-04-12 00:17