Come si usa Assert per verificare che sia stata generata un’eccezione?

Come si usa Assert (o un’altra class di test?) Per verificare che sia stata generata un’eccezione?

Per “Visual Studio Team Test” sembra che si applichi l’attributo ExpectedException al metodo del test.

Esempio dalla documentazione qui: Una verifica del test unitario con 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]"); } 

Di solito la tua struttura di test avrà una risposta per questo. Ma se non è abbastanza flessibile, puoi sempre farlo:

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

Come sottolinea @ Jonas, questo NON funziona per catturare una base. Eccezione:

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

Se devi assolutamente catturare Exception, devi rilanciare Assert.Fail (). Ma in realtà, questo è un segno che non dovresti scrivere a mano questo; controlla il tuo framework di test per le opzioni, o vedi se puoi lanciare un’eccezione più significativa da testare.

 catch (AssertionException) { throw; } 

Dovresti essere in grado di adattare questo approccio a qualsiasi cosa tu voglia, specificando anche quali tipi di eccezioni prendere. Se ti aspetti solo determinati tipi, completa i blocchi di catch con:

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

Il mio metodo preferito per implementare questo è scrivere un metodo chiamato Throws e usarlo come qualsiasi altro metodo Assert. Sfortunatamente, .NET non ti permette di scrivere un metodo di estensione statica, quindi non puoi usare questo metodo come se fosse effettivamente parte della build nella class Assert; basta fare un altro chiamato MyAssert o qualcosa di simile. La class si presenta così:

 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( String.Format("An exception of type {0} was expected, but not thrown", typeof(T)) ); } } } } 

Ciò significa che il tuo test unitario ha il seguente aspetto:

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

Che guarda e si comporta molto più come il resto delle syntax del tuo unit test.

Se utilizzi MSTest, che in origine non disponeva di un attributo ExpectedException , puoi eseguire questa operazione:

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

se usi NUNIT, puoi fare qualcosa del genere:

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

È anche ansible memorizzare l’eccezione generata per convalidarla ulteriormente:

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

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

Diffidare dell’uso di ExpectedException, in quanto può causare diversi problemi, come dimostrato qui:

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

E qui:

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

Se è necessario testare le eccezioni, ci sono meno modi disapprovati. È ansible utilizzare il metodo try {act / fail} catch {assert}, che può essere utile per i framework che non dispongono del supporto diretto per i test delle eccezioni diversi da ExpectedException.

Un’alternativa migliore è usare xUnit.NET, che è un framework di test unitario molto moderno, lungimirante ed estensibile che ha imparato da tutti gli altri errori e migliorato. Uno di questi miglioramenti è Assert.Throws, che fornisce una syntax molto migliore per affermare le eccezioni.

Puoi trovare xUnit.NET su github: http://xunit.github.io/

In un progetto su cui sto lavorando abbiamo un’altra soluzione per farlo.

Innanzitutto non mi piace ExpectedExceptionAttribute perché prende in considerazione la chiamata al metodo che ha causato l’eccezione.

Lo faccio con un helpermethod invece.

Test

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

Pulito, non è così;)

È un attributo sul metodo di prova … non usi Assert. Somiglia a questo:

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

È ansible scaricare un pacchetto da Nuget utilizzando: PM> Install-Package MSTestExtensions che aggiunge la syntax Assert.Throws () nello stile di nUnit / xUnit a MsTest.

Istruzioni di alto livello: scarica l’assembly ed erediti da BaseTest e puoi utilizzare la syntax Assert.Throws () .

Il metodo principale per l’implementazione di Throws è il seguente:

 public static void Throws(Action task, string 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(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T))); } } 

Divulgazione: ho messo insieme questo pacchetto.

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

MSTest ora ha una funzione Assert.ThrowsException che può essere utilizzata in questo modo:

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

Non è consigliabile utilizzare l’attributo ExpectedException (poiché è troppo vincolante e sobject a errori) o scrivere un blocco try / catch in ogni test (poiché è troppo complicato e sobject a errori). Utilizzare un metodo di asserzione ben progettato, fornito dal framework di test o scritto da soli. Ecco cosa ho scritto e usato.

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

Esempi di utilizzo:

  [TestMethod] public void Run_PropagatesWin32Exception_ForInvalidExeFile() { (test setup that might propagate Win32Exception) ExceptionAssert.Propagates( () => 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( () => CommandExecutionUtil.Run("NotThere.exe", new string[0]), e => StringAssert.Contains(e.Message, "NotThere.exe")); (more asserts or something) } 

GLI APPUNTI

Restituire l’eccezione invece di supportare un callback di convalida è un’idea ragionevole, tranne per il fatto che così facendo la syntax di chiamata di questa asserzione è molto diversa da quella degli altri asserzioni che uso.

A differenza di altri, io uso “si propaga” invece di “getta” poiché possiamo solo verificare se un’eccezione si propaga da una chiamata. Non possiamo testare direttamente che venga lanciata un’eccezione. Ma suppongo che potresti immaginare tiri per significare: lanciati e non catturati.

PENSIERO FINALE

Prima di passare a questo tipo di approccio, ho preso in considerazione l’utilizzo dell’attributo ExpectedException quando un test ha verificato il tipo di eccezione e utilizzava un blocco try / catch se era necessaria più convalida. Ma non solo dovrei pensare a quale tecnica usare per ogni test, ma cambiare il codice da una tecnica all’altra in base alle esigenze cambiate non è stato uno sforzo banale. L’utilizzo di un approccio coerente consente di risparmiare sforzi mentali.

Quindi, in sintesi, questo approccio mette in mostra: facilità d’uso, flessibilità e robustezza (difficile da sbagliare).

L’helper fornito da @Richiban sopra funziona alla grande, tranne che non gestisce la situazione in cui viene lanciata un’eccezione, ma non il tipo previsto. I seguenti indirizzi che:

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

Bene, riassumo quello che tutti gli altri hanno detto prima … Comunque, ecco il codice che ho costruito secondo le buone risposte 🙂 Tutto ciò che resta da fare è copiare e usare …

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

Poiché si menziona l’utilizzo di altre classi di test, un’opzione migliore rispetto all’attributo ExpectedException è l’uso di Should.Throw di Shoudly .

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

Diciamo che abbiamo il requisito che il cliente deve avere un indirizzo per creare un ordine . In caso contrario, il metodo CreateOrderForCustomer dovrebbe generare una ArgumentException . Quindi potremmo scrivere:

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

Questo è meglio dell’utilizzo di un attributo ExpectedException perché siamo specifici su ciò che dovrebbe generare l’errore. Ciò rende più chiari i requisiti dei nostri test e facilita la diagnosi anche in caso di fallimento del test.

Nota c’è anche un Should.ThrowAsync per il test del metodo asincrono.

Consulta nUnit Documenti per esempi su:

 [ExpectedException( typeof( ArgumentException ) )] 

Questo dipenderà da quale framework di test stai usando?

In MbUnit, ad esempio, puoi specificare l’eccezione prevista con un attributo per assicurarti di ottenere l’eccezione che ti aspetti veramente.

 [ExpectedException(typeof(ArgumentException))] 

In alternativa puoi provare a provare che le eccezioni vengono lanciate con le prossime 2 righe nel test.

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

In caso di utilizzo di NUnit , prova questo:

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

Nel test dell’unità VS integrato, se si desidera semplicemente verificare che venga lanciata “qualsiasi eccezione”, ma non si conosce il tipo, è ansible utilizzare un catch all:

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

Anche se questa è una vecchia domanda, vorrei aggiungere un nuovo pensiero alla discussione. Ho esteso il modello Disponi, Agisci, Assert da aspettarsi, Disponi, Agisci, Assert. È ansible creare un puntatore di eccezioni previsto, quindi affermare che è stato assegnato a. Questo sembra più pulito rispetto a fare i tuoi Assert in un blocco catch, lasciando la tua sezione Act principalmente per la sola riga di codice per chiamare il metodo in prova. Inoltre non devi Assert.Fail(); o return da più punti nel codice. Qualsiasi altra eccezione generata causerà il fallimento del test, perché non verrà rilevato e se viene lanciata un’eccezione del tipo previsto, ma non era quella che si aspettava, Asserting contro il messaggio o altre proprietà di l’eccezione aiuta a garantire che il test non passi inavvertitamente.

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