Wie verwende ich
Assert
(oder eine andere Testklasse?), 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?
Antworten:
Für "Visual Studio Team Test" wenden Sie anscheinend das ExpectedException-Attribut auf die Testmethode an.
Beispiel aus der Dokumentation hier: Eine exemplarische Vorgehensweise 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, "P@ss0word");
}
Normalerweise hat Ihr Test-Framework eine Antwort darauf. Aber 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) { }
Wie @Jonas hervorhebt, funktioniert dies NICHT, um eine Basis zu fangen. 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 auslösen. Aber wirklich, das ist ein Zeichen, dass Sie dies nicht von Hand schreiben sollten. Überprüfen Sie Ihr Testframework 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 Ihre Bedürfnisse anzupassen - einschließlich der Angabe, welche Arten von Ausnahmen zu fangen 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();
}
Meine bevorzugte Methode, um 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, sodass Sie diese Methode nicht so verwenden können, als ob sie tatsächlich zur Build-in-Assert-Klasse gehört. 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 Komponententest folgendermaßen aussieht:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Das sieht und verhält sich viel mehr wie der Rest Ihrer Unit-Test-Syntax.
Assert.ThrowsException<T>
und Assert.ThrowsExceptionAsync<T>
- siehe blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
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 validieren:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
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);
}
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 auf Ausnahmen testen müssen, gibt es weniger verpönte Möglichkeiten. Sie können die Methode try {act / fail} catch {assert} verwenden, die für Frameworks hilfreich sein kann, die andere Ausnahmetests als ExpectedException nicht direkt unterstützen.
Eine bessere Alternative ist die Verwendung von xUnit.NET, einem sehr modernen, zukunftsorientierten und erweiterbaren Unit-Test-Framework, das aus allen anderen Fehlern gelernt und verbessert hat. Eine solche Verbesserung ist Assert.Throws, die eine viel bessere Syntax zum Aktivieren von Ausnahmen bietet.
Sie finden xUnit.NET unter github: http://xunit.github.io/
MSTest (v2) verfügt jetzt über eine Assert.ThrowsException-Funktion, die wie folgt 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
In einem Projekt, an dem ich arbeite, haben wir eine andere Lösung dafür.
Zuerst mag ich das ExpectedExceptionAttribute nicht, weil es berücksichtigt, welcher Methodenaufruf die Exception 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?
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()
Sie können ein Paket von Nuget herunterladen, indem Sie: PM> Install-Package MSTestExtensions verwenden , das Assert.Throws () hinzufügt. Syntax im Stil von nUnit / xUnit hinzuzufügen.
Allgemeine Anweisungen: Laden Sie die Assembly herunter und erben Sie sie von BaseTest. Sie können dann Assert.Throws () verwenden. Syntax .
Die Hauptmethode für die Throws-Implementierung sieht wie folgt 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
Sie können dies mit einer einfachen einzeiligen erreichen.
Wenn Ihre Operation foo.bar()
asynchron ist:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Wenn foo.bar()
ist nicht asynchron
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
. Der alte Versuch, die Ausnahmeantwort abzufangen und zu testen, wird immer noch bevorzugt, wenn Sie erweiterte Kriterien zum Testen haben, aber in vielen meiner Fälle hilft dies sehr!
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 konzipierte Assert-Methode - entweder von Ihrem Test-Framework bereitgestellt oder schreiben Sie Ihre eigene. Folgendes habe ich geschrieben und verwendet.
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
Das Zurückgeben der Ausnahme anstelle der Unterstützung eines Validierungsrückrufs ist eine vernünftige Idee, mit der Ausnahme, dass sich dadurch die aufrufende Syntax dieser Behauptung stark von anderen von mir verwendeten Behauptungen unterscheidet.
Im Gegensatz zu anderen verwende ich "Propagates" anstelle von "Throws", da wir nur testen können, ob sich eine Ausnahme von einem Aufruf ausbreitet. Wir können nicht direkt testen, ob eine Ausnahme ausgelöst wird. Aber ich nehme an, Sie könnten sich Würfe vorstellen, die bedeuten: geworfen und nicht gefangen.
LETZTER GEDANKE
Bevor ich zu diesem Ansatz wechselte, habe ich erwogen, das Attribut ExpectedException zu verwenden, wenn ein Test nur den Ausnahmetyp verifizierte, und einen Try / Catch-Block zu verwenden, wenn weitere Validierungen erforderlich waren. Aber ich musste nicht nur darüber nachdenken, welche Technik für jeden Test verwendet werden sollte, sondern es war auch kein trivialer Aufwand, den Code von einer Technik zur anderen zu ändern, wenn sich die Anforderungen änderten. Die Verwendung eines einheitlichen Ansatzes spart geistige Anstrengung.
Zusammenfassend ist dieser Ansatz sportlich: Benutzerfreundlichkeit, Flexibilität und Robustheit (schwer falsch zu machen).
Der von @Richiban oben bereitgestellte Helfer funktioniert hervorragend, außer er behandelt nicht die Situation, in der eine Ausnahme ausgelöst wird, aber nicht den erwarteten Typ. Die folgenden Adressen, die:
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))
);
}
}
}
}
Da Sie die Verwendung anderer Testklassen erwähnen , ExpectedException
ist die Verwendung von Shoudlys Should.Throw eine bessere Option als das Attribut .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Angenommen, der Kunde muss eine Adresse haben , um eine Bestellung zu erstellen . Wenn nicht, sollte die CreateOrderForCustomer
Methode zu einem führen ArgumentException
. 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
asynchronen Methodentest gibt.
Wenn Sie beim integrierten VS-Komponententest lediglich überprüfen möchten, ob "eine Ausnahme" ausgelöst wird, den Typ jedoch nicht kennen, können Sie einen catch all verwenden:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
Nun, ich fasse ziemlich genau zusammen, was alle anderen hier zuvor gesagt haben ... Wie auch immer, hier ist der Code, den ich gemäß den guten Antworten erstellt habe :) Alles, was zu tun bleibt, ist zu kopieren und zu verwenden ...
/// <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.");
}
In den nUnit-Dokumenten finden Sie Beispiele zu folgenden Themen :
[ExpectedException( typeof( ArgumentException ) )]
Versuchen Sie bei Verwendung von NUnit Folgendes :
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Es gibt eine großartige Bibliothek namens NFluent , die das Schreiben Ihrer Behauptungen beschleunigt und vereinfacht .
Es ist ziemlich einfach, eine Behauptung zu schreiben, um eine Ausnahme auszulösen:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Auch wenn dies eine alte Frage ist, möchte ich der Diskussion einen neuen Gedanken hinzufügen. Ich habe das zu erwartende Muster "Anordnen, Handeln, Bestätigen", "Anordnen, Handeln, Bestätigen" erweitert. Sie können einen erwarteten Ausnahmezeiger erstellen und dann bestätigen, dass er zugewiesen wurde. Dies fühlt sich sauberer an, als Ihre Asserts in einem Catch-Block auszuführen, sodass Ihr Act-Abschnitt meist nur für die eine Codezeile übrig bleibt, um die zu testende Methode aufzurufen. Sie müssen auch nicht zu Assert.Fail();
oder return
von mehreren Punkten im Code. Jede andere ausgelöste Ausnahme führt dazu, dass der Test fehlschlägt, da er nicht abgefangen wird. Wenn eine Ausnahme Ihres erwarteten Typs ausgelöst wird, diese jedoch nicht die erwartete war, wird dies gegen die Nachricht oder andere Eigenschaften von bestätigt Mit der Ausnahme stellen Sie sicher, dass Ihr Test nicht versehentlich 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);
}