Comment utiliser Assert pour vérifier qu’une exception a été lancée?

Comment utiliser Assert (ou une autre classe de test?) Pour vérifier qu’une exception a été lancée?

Pour “Visual Studio Team Test”, il apparaît que vous appliquez l’atsortingbut ExpectedException à la méthode du test.

Exemple de la documentation ici: Une procédure de test d’unité avec 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"); } 

Habituellement, votre structure de test aura une réponse à cela. Mais si ce n’est pas assez flexible, vous pouvez toujours le faire:

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

Comme @Jonas le fait remarquer, cela ne fonctionne PAS pour attraper une exception de base:

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

Si vous devez absolument intercepter Exception, vous devez relancer Assert.Fail (). Mais vraiment, ceci est un signe que vous ne devriez pas écrire ceci à la main; vérifiez votre structure de test pour les options ou voyez si vous pouvez lancer une exception plus significative à tester.

 catch (AssertionException) { throw; } 

Vous devriez pouvoir adapter cette approche à ce que vous voulez, notamment en spécifiant les types d’exceptions à prendre. Si vous ne vous attendez qu’à certains types, terminez les blocs avec:

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

Ma méthode préférée pour l’implémenter est d’écrire une méthode appelée Throws et de l’utiliser comme n’importe quelle autre méthode Assert. Malheureusement, .NET ne vous permet pas d’écrire une méthode d’extension statique, vous ne pouvez donc pas utiliser cette méthode comme si elle appartenait réellement à la classe Assert; Il suffit de faire un autre appelé MyAssert ou quelque chose de similaire. La classe ressemble à ceci:

 using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace YourProject.Tests { public static class MyAssert { public static void Throws( Action func ) where T : Exception { var exceptionThrown = false; try { func.Invoke(); } catch ( T ) { exceptionThrown = true; } if ( !exceptionThrown ) { throw new AssertFailedException( Ssortingng.Format("An exception of type {0} was expected, but not thrown", typeof(T)) ); } } } } 

Cela signifie que votre test unitaire ressemble à ceci:

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

Qui ressemble et se comporte beaucoup plus comme le rest de vos syntaxes de test unitaire.

Si vous utilisez MSTest, qui à l’origine ne possédait pas d’atsortingbut ExpectedException , vous pouvez le faire:

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

Si vous utilisez NUNIT, vous pouvez faire quelque chose comme ceci:

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

Il est également possible de stocker l’exception levée afin de la valider davantage:

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

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

Méfiez-vous d’utiliser ExpectedException, car cela peut entraîner plusieurs pièges, comme démontré ici:

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

Et ici:

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

Si vous avez besoin de tester des exceptions, il y a moins de manières mal vues. Vous pouvez utiliser la méthode try {act / fail} catch {assert}, qui peut être utile pour les frameworks qui ne prennent pas directement en charge les tests d’exception autres que ExpectedException.

Une meilleure alternative est d’utiliser xUnit.NET, qui est un framework de test unitaire très moderne, tourné vers l’avenir et extensible, qui a appris de toutes les autres erreurs et amélioré. Une de ces améliorations est Assert.Throws, qui fournit une syntaxe bien meilleure pour affirmer des exceptions.

Vous pouvez trouver xUnit.NET à github: http://xunit.github.io/

Dans un projet sur lequel je travaille, nous avons une autre solution.

Tout d’abord, je n’aime pas le ExpectedExceptionAtsortingbute car cela prend en compte l’appel de méthode qui a provoqué l’Exception.

Je le fais avec une méthode d’aide à la place.

Tester

 [TestMethod] public void AccountRepository_ThrowsExceptionIfFileisCorrupt() { var file = File.Create("Accounts.bin"); file.WriteByte(1); file.Close(); IAccountRepository repo = new FileAccountRepository(); TestHelpers.AssertThrows(()=>repo.GetAll()); } 

HelperMethod

 public static TException AssertThrows(Action action) where TException : Exception { try { action(); } catch (TException ex) { return ex; } Assert.Fail("Expected exception was not thrown"); return null; } 

Neat, n’est-ce pas;)

C’est un atsortingbut de la méthode de test … vous n’utilisez pas Assert. Ressemble à ça:

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

Vous pouvez télécharger un package à partir de Nuget à l’aide de: PM> Install-Package MSTestExtensions qui ajoute la syntaxe Assert.Throws () dans le style de nUnit / xUnit à MsTest.

Instructions de haut niveau: téléchargez l’assembly et héritez de BaseTest et vous pouvez utiliser la syntaxe Assert.Throws () .

La méthode principale de l’implémentation Throws est la suivante:

 public static void Throws(Action task, ssortingng expectedMessage, ExceptionMessageCompareOptions options) where T : Exception { try { task(); } catch (Exception ex) { AssertExceptionType(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(ssortingng.Format("Expected exception of type {0} but no exception was thrown.", typeof(T))); } } 

Divulgation: j’ai assemblé ce paquet.

Plus d’informations: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html

MSTest a maintenant une fonction Assert.ThrowsException qui peut être utilisée comme ceci:

 Assert.ThrowsException(() => { Story actual = PersonalSite.Services.Content.ExtractHeader(Ssortingng.Empty); }); 

Je ne recommande pas d’utiliser l’atsortingbut ExpectedException (car il est trop contraignant et sujet aux erreurs) ou d’écrire un bloc try / catch dans chaque test (car il est trop compliqué et sujet aux erreurs). Utilisez une méthode d’assertion bien conçue – fournie par votre infrastructure de test ou écrivez la vôtre. Voici ce que j’ai écrit et utilisé.

 public static class ExceptionAssert { private static T GetException(Action action, ssortingng 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(Action action) where T : Exception { Propagates(action, ""); } public static void Propagates(Action action, ssortingng message) where T : Exception { GetException(action, message); } public static void Propagates(Action action, Action validation) where T : Exception { Propagates(action, validation, ""); } public static void Propagates(Action action, Action validation, ssortingng message) where T : Exception { validation(GetException(action, message)); } } 

Exemple utilise:

  [TestMethod] public void Run_PropagatesWin32Exception_ForInvalidExeFile() { (test setup that might propagate Win32Exception) ExceptionAssert.Propagates( () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new ssortingng[0])); (more asserts or something) } [TestMethod] public void Run_PropagatesFileNotFoundException_ForExecutableNotFound() { (test setup that might propagate FileNotFoundException) ExceptionAssert.Propagates( () => CommandExecutionUtil.Run("NotThere.exe", new ssortingng[0]), e => SsortingngAssert.Contains(e.Message, "NotThere.exe")); (more asserts or something) } 

REMARQUES

Renvoyer l’exception au lieu de prendre en charge un rappel de validation est une idée raisonnable, sauf que cela fait que la syntaxe d’appel de cette affirmation est très différente de celle des autres assertions que j’utilise.

Contrairement à d’autres, j’utilise «propagates» au lieu de «throws», car nous ne pouvons que tester si une exception se propage à partir d’un appel. Nous ne pouvons pas tester directement qu’une exception est levée. Mais je suppose que vous pourriez imaginer des images: jetées et non attrapées.

PENSÉE FINALE

Avant de passer à ce type d’approche, j’ai envisagé d’utiliser l’atsortingbut ExpectedException lorsqu’un test vérifiait uniquement le type d’exception et utilisait un bloc try / catch si une validation supplémentaire était requirejse. Mais, non seulement je devais réfléchir à la technique à utiliser pour chaque test, mais changer le code d’une technique à l’autre en fonction des besoins n’était pas une mince affaire. Utiliser une approche cohérente permet d’économiser l’effort mental.

Donc, en résumé, cette approche est la suivante: facilité d’utilisation, flexibilité et robustesse (difficile à faire mal).

L’assistant fourni par @Richiban ci-dessus fonctionne très bien, sauf qu’il ne gère pas la situation dans laquelle une exception est lancée, mais pas le type attendu. Les adresses suivantes qui:

 using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace YourProject.Tests { public static class MyAssert { ///  /// Helper for Asserting that a function throws an exception of a particular type. ///  public static void Throws( 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( Ssortingng.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( Ssortingng.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T)) ); } } } } 

Eh bien, je vais résumer à peu près tout ce que tout le monde ici a dit auparavant … En tout cas, voici le code que j’ai construit en fonction des bonnes réponses 🙂 Il ne rest plus qu’à copier et utiliser …

 ///  /// Checks to make sure that the input delegate throws a exception of type TException. ///  /// The type of exception expected. /// The method to execute to generate the exception. public static void AssertRaises(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."); } 

Comme vous parlez d’utiliser d’autres classes de test, une meilleure option que l’atsortingbut ExpectedException est d’utiliser Shoudly ‘s Should.Throw .

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

Supposons que le client doit avoir une adresse pour créer une commande . Sinon, la méthode CreateOrderForCustomer doit entraîner une CreateOrderForCustomer ArgumentException . Ensuite, nous pourrions écrire:

 [TestMethod] public void NullUserIdInConstructor() { var customer = new Customer(name := "Justin", address := null}; Should.Throw(() => { var order = CreateOrderForCustomer(customer) }); } 

C’est mieux que d’utiliser un atsortingbut ExpectedException car nous sums précis sur ce qui devrait générer l’erreur. Cela rend les exigences plus claires dans nos tests et facilite également le diagnostic lorsque le test échoue.

Notez qu’il existe également un Should.ThrowAsync pour le test de méthode asynchrone.

Découvrez nUnit Docs pour des exemples sur:

 [ExpectedException( typeof( ArgumentException ) )] 

Cela va dépendre de quel framework de test utilisez-vous?

Dans MbUnit, par exemple, vous pouvez spécifier l’exception attendue avec un atsortingbut pour vous assurer que vous obtenez l’exception que vous attendez vraiment.

 [ExpectedException(typeof(ArgumentException))] 

Comme alternative, vous pouvez essayer de tester les exceptions qui sont en fait lancées avec les 2 lignes suivantes dans votre test.

 var testDelegate = () => MyService.Method(params); Assert.Throws(testDelegate); 

Si vous utilisez NUnit , essayez ceci:

 Assert.That(() => { Your_Method_To_Test(); }, Throws.TypeOf().With.Message.EqualTo("Your_Specific_Message")); 

Dans les tests unitaires intégrés de VS, si vous voulez simplement vérifier que “une exception” est émise, mais que vous ne connaissez pas le type, vous pouvez utiliser un catch all:

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

Même s’il s’agit d’une vieille question, j’aimerais append une nouvelle reflection à la discussion. J’ai prolongé le modèle d’arrangement, d’acte, d’affirmation à prévoir, organiser, agir, affirmer. Vous pouvez créer un pointeur d’exception attendu, puis l’affirmer. Cela semble plus propre que de faire vos Assert dans un bloc catch, en laissant votre section Act principalement juste pour la ligne de code à appeler la méthode en cours de test. Vous n’avez pas non plus à Assert.Fail(); ou return de plusieurs points dans le code. Toute autre exception déclenchée entraînera l’échec du test, car elle ne sera pas interceptée, et si une exception de votre type attendu est émise, mais ce n’est pas celle que vous attendiez, Affirmant le message ou d’autres propriétés de l’exception permet de s’assurer que votre test ne passera pas par inadvertance.

 [TestMethod] public void Bar_InvalidDependency_ThrowsInvalidOperationException() { // Expectations InvalidOperationException expectedException = null; ssortingng 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); }