2009-04-12 2 views

Antwort

114

Ich habe ein paar verschiedene Muster, die Ich benutze. Ich verwende das Attribut ExpectedException 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 einen try/catch Block, der die exakte Ausnahme erwartet, tut eine Assert.Fail, wenn der Code erfolgreich ist und fängt auch generische Ausnahmen ab, um sicherzustellen, dass eine andere Ausnahme nicht 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) 
     ); 
    } 
} 
+15

Viele Unit-Test-Frameworks implementieren Assertionsfehler als Ausnahmen. So wird die Assert.Fail() im zweiten Fall vom Catch-Block (Exception) abgefangen, der die Ausnahmebedingungsnachricht ausblendet. Sie müssen einen Catch hinzufügen (NUnit.Framework.AssertionException) {throw;} oder ähnlich - siehe meine Antwort. – GrahamS

+0

@Graham - Ich tippte mir das aus dem Kopf. Normalerweise würde ich die Ausnahmebedingungsnachricht zusätzlich zu ihrem Typ ausdrucken. Der Punkt ist, dass der Test fehlschlagen würde, da der zweite Handler den Assertionsfehler abfangen und mit Informationen über den Fehler "umkehren" würde. – tvanfosson

+1

Obwohl Ihr Code funktional klingt, empfehle ich nicht, das ExpectedException-Attribut zu verwenden (da es zu einschränkend und fehleranfällig ist) oder einen try/catch-Block in jedem Test zu schreiben (da es zu kompliziert und fehleranfällig ist). Verwenden Sie eine gut durchdachte Assert-Methode - entweder von Ihrem Test-Framework zur Verfügung gestellt oder schreiben Sie Ihre eigenen. Sie können einen besseren Code erzielen, und Sie müssen nicht zwischen den verschiedenen Techniken wählen oder von einem zum anderen wechseln, wenn sich der Test ändert. Siehe http://StackOverflow.com/A/25084462/2166177 – steve

4

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

+0

sie ExpectedExceptionAttribute (Grund in meinem Beitrag weiter unten) nicht nutzen. NUnit hat Assert.Auslöser () und für MSTest verwenden etwas wie meine AssertException-Klasse unten. – nashwan

2

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.net helfen kann.

Mit xunit Sie Assert.Throws haben, so dass Sie Dinge wie diese tun können:

[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] wird die xunit Äquivalent [Testmethod]

+0

Wenn Sie MSTest verwenden müssen (zu dem ich oft von Arbeitgebern gezwungen werde), dann sehen Sie meine Antwort unten. – nashwan

9

Als Alternative ExpectedException Attribut zu verwenden, definiere ich manchmal zwei hilfreiche Methoden für meine Testklassen:

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

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 in einem Fall eine Ausnahme ausgelöst wird, aber nicht in der anderen.

Mit ihnen meine Unit-Test-Code könnte 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); 

Nizza und ordentlich huh?

Meine AssertThrowsException() und AssertDoesNotThrowException() Methoden sind 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); 
    } 
} 
15

Ich bin neu hier und haben nicht den Ruf zu kommentieren oder downvote, wollte aber einen Fehler hinweisen, im Beispiel in Andy White's reply:

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

in all Test-Frameworks Einheit I vertraut sind mit, Assert.Fail Arbeiten eine Ausnahme zu werfen, so dass der generische Fang tatsächlich das Scheitern der Testmaske wird. Wenn SomethingThatCausesAnException() nicht wirft, wird die Assert.Fail werden, aber das wird nie zum Test-Runner sprudeln, um einen Fehler anzuzeigen.

Wenn Sie die erwartete Ausnahme abfangen müssen (d. H. Bestimmte Details wie die Nachricht/Eigenschaften in der Ausnahme bestätigen), ist es wichtig, den bestimmten erwarteten Typ und nicht die Basis-Exception-Klasse abzufangen. Das würde ermöglichen, dass die Assert.Fail-Ausnahme überspringt (vorausgesetzt, Sie werfen nicht dieselbe Art von Ausnahme wie das Unit-Testframework), aber die Validierung der Ausnahme, die von Ihrer SomethingThatCausesAnException()-Methode ausgelöst wurde, weiterhin zulässt.

12

Ab v 2.5, NUnit hat die folgende Methode Ebene Assert s für die Prüfung Ausnahmen:

Assert.Throws, die für eine genaue Ausnahmetyp testen:

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

Und Assert.Catch, die für eine testen Ausnahme eines bestimmten Typs oder eines von diesem Typ abgeleiteten Ausnahmetyps:

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

Wenn Sie Debugger-Unit-Tests ausführen, die Exceptions auslösen, sollten Sie unter Umständen VS von breaking on the exception verhindern.

bearbeiten

Gerade unter einem Beispiel des Matthäus Kommentars zu geben, die Rückkehr der generischen Assert.Throws und Assert.Catch ist die Ausnahme mit der Art der Ausnahme, die Sie für eine weitere Inspektion dann prüfen können:

+1

Roy Osherove empfiehlt dies in der Art of Unit Testing, zweite Auflage, Abschnitt 2.6.2. – Avi

+2

Ich mag 'Assert.Throws', zusätzlich gibt es die Ausnahme zurück, so dass Sie weitere Aussagen über die Ausnahme selbst schreiben können. – Matthew

+0

Die Frage war für MSTest nicht NUnit. – nashwan

8

Leider hat MSTest STILL nur wirklich das ExpectedException-Attribut (zeigt nur, wie viel MS sich um MSTest kümmert), was IMO ziemlich schrecklich ist, weil es das Arrange/Act/Assert-Muster unterbricht und es nicht erlaubt, exac zu spezifizieren Welche Codezeile Sie erwarten, dass die Ausnahme auftritt.

Wenn ich verwende (/ gezwungen von einem Client) MSTest zu verwenden, habe ich immer diese Hilfsklasse verwenden:

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

Jetzt, 2017, können Sie sie es leichter, mit dem neuen MSTest V2 Framework:

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