Come si utilizza
Assert
(o altra classe di test?) Per verificare che sia stata generata un'eccezione?
Come si utilizza
Assert
(o altra classe di test?) Per verificare che sia stata generata un'eccezione?
Risposte:
Per "Visual Studio Team Test" sembra che si applichi l'attributo ExpectedException al metodo del test.
Esempio dalla documentazione qui: A Test unit Unit Soluzione con 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");
}
Di solito il tuo framework 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 rilevare un'eccezione di base:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // raises AssertionException
} catch (Exception) {
// Catches the assertion exception, and the test passes
}
Se è assolutamente necessario rilevare l'eccezione, è necessario ricodificare Assert.Fail (). Ma davvero, questo è un segno che non dovresti scriverlo a mano; controlla le opzioni del tuo framework di test o vedi se riesci a lanciare un'eccezione più significativa da testare.
catch (AssertionException) { throw; }
Dovresti essere in grado di adattare questo approccio a quello che ti piace, incluso specificare quali tipi di eccezioni catturare. Se ti aspetti solo alcuni tipi, finisci i catch
blocchi 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 consente di scrivere un metodo di estensione statica, quindi non è possibile utilizzare questo metodo come se appartenesse effettivamente alla build nella classe Assert; basta crearne un altro chiamato MyAssert o qualcosa di simile. La classe si presenta così:
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))
);
}
}
}
}
Ciò significa che il test unitario è simile al seguente:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Che assomiglia e si comporta in modo molto più simile al resto delle sintassi del test unitario.
Assert.ThrowsException<T>
e Assert.ThrowsExceptionAsync<T>
- vedi blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
se usi NUNIT, puoi fare qualcosa del genere:
Assert.Throws<ExpectedException>(() => methodToTest());
È anche possibile memorizzare l'eccezione generata per convalidarla ulteriormente:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Se stai usando MSTest, che in origine non aveva un ExpectedException
attributo, potresti farlo:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Diffidare di utilizzare ExpectedException, in quanto può portare a diverse insidie, 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 hai bisogno di verificare le eccezioni, ci sono meno modi corretti. È possibile utilizzare il metodo try {act / fail} catch {assert}, che può essere utile per i framework che non dispongono di supporto diretto per i test delle eccezioni diversi da ExpectedException.
Un'alternativa migliore è usare xUnit.NET, che è un framework di test di unità molto moderno, lungimirante ed estensibile che ha imparato da tutti gli altri errori e migliorato. Uno di questi miglioramenti è Assert.Throws, che fornisce una sintassi molto migliore per affermare le eccezioni.
Puoi trovare xUnit.NET su github: http://xunit.github.io/
MSTest (v2) ora ha una funzione Assert.ThrowsException che può essere utilizzata in questo modo:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Puoi installarlo con nuget: Install-Package MSTest.TestFramework
In un progetto a cui sto lavorando abbiamo un'altra soluzione per farlo.
Innanzitutto non mi piace ExpectedExceptionAttribute perché prende in considerazione la chiamata del metodo che ha causato l'eccezione.
Lo faccio invece con un helpermethod.
Test
[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;
}
Pulito, non è vero;)
È un attributo sul metodo di test ... non usi Assert. Somiglia a questo:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Puoi scaricare un pacchetto da Nuget usando: PM> Installa-pacchetto MSTestExtensions che aggiunge Assert.Throws () sintassi nello stile di nUnit / xUnit a MsTest.
Istruzioni di alto livello: scarica l'assembly ed eredita da BaseTest e puoi usare Assert.Throws () sintassi .
Il metodo principale per l'implementazione di Throws è il seguente:
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)));
}
}
Divulgazione: ho messo insieme questo pacchetto.
Ulteriori informazioni: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html
Puoi raggiungerlo con una semplice riga.
Se l'operazione foo.bar()
è asincrona:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Se foo.bar()
non è asincrono
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
esempio. Il vecchio Try Catch e test della risposta alle eccezioni è ancora preferito se hai criteri avanzati da testare, ma per molti dei miei casi, questo aiuta molto!
Non consiglio di utilizzare l'attributo ExpectedException (poiché è troppo vincolante e soggetto a errori) o di scrivere un blocco try / catch in ogni test (poiché è troppo complicato e soggetto a errori). Utilizzare un metodo di asserzione ben progettato, fornito dal framework di test o scrivere il proprio. Ecco cosa ho scritto e usato.
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));
}
}
L'esempio usa:
[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)
}
APPUNTI
Restituire l'eccezione invece di supportare un callback di convalida è un'idea ragionevole, tranne per il fatto che la sintassi della chiamata di questa asserzione è molto diversa rispetto alle altre asserzioni che uso.
A differenza di altri, utilizzo "propagati" anziché "tiri" poiché possiamo solo verificare se un'eccezione si propaga da una chiamata. Non possiamo testare direttamente che viene generata un'eccezione. Ma suppongo che tu possa immaginare dei tiri per dire: lanciati e non catturati.
PENSIERO FINALE
Prima di passare a questo tipo di approccio ho considerato l'utilizzo dell'attributo ExpectedException quando un test ha verificato solo il tipo di eccezione e l'utilizzo di un blocco try / catch se è stata richiesta una maggiore 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'uso di un approccio coerente consente di risparmiare sforzo mentale.
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 generata 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
{
/// <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))
);
}
}
}
}
Dato che accenni all'uso di altre classi di test, un'opzione migliore ExpectedException
dell'attributo è quella di utilizzare Shoudly 's Should.Throw .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Diciamo che abbiamo l'obbligo che il cliente abbia un indirizzo per creare un ordine . In caso contrario, il CreateOrderForCustomer
metodo dovrebbe risultare in un ArgumentException
. Quindi potremmo scrivere:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
Questo è meglio che usare un ExpectedException
attributo perché siamo precisi su cosa dovrebbe generare l'errore. Ciò rende più chiari i requisiti nei nostri test e semplifica anche la diagnosi quando il test fallisce.
Si noti che esiste anche un Should.ThrowAsync
test del metodo asincrono.
Nel VS unit test integrato se si desidera semplicemente verificare che venga generata "qualsiasi eccezione", ma non si conosce il tipo, è possibile utilizzare un catch all:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
Bene, riassumerò praticamente quello che tutti gli altri qui hanno detto prima ... Comunque, ecco il codice che ho costruito secondo le buone risposte :) Tutto ciò che resta da fare è copiare e usare ...
/// <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.");
}
Dai un'occhiata a nUnit Docs per esempi su:
[ExpectedException( typeof( ArgumentException ) )]
In caso di utilizzo di NUnit , prova questo:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
C'è una fantastica libreria chiamata NFluent che accelera e facilita il modo in cui scrivi le tue affermazioni .
È abbastanza semplice scrivere un'asserzione per lanciare un'eccezione:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Anche se questa è una vecchia domanda, vorrei aggiungere un nuovo pensiero alla discussione. Ho esteso il modello Arrange, Act, Assert per essere previsto, Arrange, Act, Assert. È possibile creare un puntatore eccezione previsto, quindi affermare che è stato assegnato. Questo sembra più pulito che eseguire i tuoi Assert in un blocco catch, lasciando la sezione Act principalmente solo per una riga di codice per chiamare il metodo sotto test. Inoltre non è necessario Assert.Fail();
o return
da più punti nel codice. Qualsiasi altra eccezione generata provocherà il fallimento del test, perché non verrà rilevata e se viene generata un'eccezione del tipo previsto, ma non era quella che ti aspettavi, affermando 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);
}