2009-04-12 9 views

risposta

114

Ho un paio di modelli diversi che Io uso. Uso l'attributo ExpectedException la maggior parte delle volte quando è prevista un'eccezione. Questo è sufficiente nella maggior parte dei casi, tuttavia, ci sono alcuni casi in cui questo non è sufficiente. L'eccezione potrebbe non essere catchable - poiché è lanciata da un metodo invocato dalla riflessione - o forse voglio solo controllare che altre condizioni siano valide, ad esempio una transazione viene annullata o qualche valore è ancora stato impostato. In questi casi, lo inserisco in un blocco try/catch che si aspetta l'eccezione esatta, fa un Assert.Fail se il codice riesce e rileva anche eccezioni generiche per assicurarsi che non venga generata un'eccezione diversa.

Primo caso:

[TestMethod] 
[ExpectedException(typeof(ArgumentNullException))] 
public void MethodTest() 
{ 
    var obj = new ClassRequiringNonNullParameter(null); 
} 

Secondo caso:

[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

Molti framework di test delle unità implementano errori di asserzione come eccezioni. Quindi Assert.Fail() nel secondo caso verrà catturato dal blocco catch (Exception), che nasconderà il messaggio di eccezione. È necessario aggiungere una presa (NUnit.Framework.AssertionException) {throw;} o simile - vedere la mia risposta. – GrahamS

+0

@Graham - Ho scritto questo in cima alla mia testa. Ordinariamente vorrei anche stampare il messaggio di eccezione oltre al suo tipo. Il punto è che il test fallirebbe dato che il secondo handler avrebbe colto l'errore di asserzione e "refail" con informazioni sull'errore. – tvanfosson

+1

Sebbene il codice sia funzionalmente valido, non è consigliabile utilizzare l'attributo ExpectedException (poiché è troppo vincolante e soggetto a errori) o 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 scritto da soli. Puoi ottenere un codice migliore e non dovrai scegliere tra le diverse tecniche o passare da una all'altra mentre il test cambia. Vedi http://stackoverflow.com/a/25084462/2166177 – steve

4

Contrassegnare il test con ExpectedExceptionAttribute (questo è il termine in NUnit o MSTest; gli utenti di altri framework di testing dell'unità potrebbero dover tradurre).

+0

non utilizzare ExpectedExceptionAttribute (motivazione addotta nel mio post qui sotto). NUnit ha dichiarato.Genera () e per MSTest usa qualcosa come la mia classe AssertException di seguito. – nashwan

2

Con la maggior parte dei framework di test delle unità .net è possibile inserire un attributo [ExpectedException] nel metodo di test. Tuttavia questo non può dirvi che l'eccezione è avvenuta nel punto in cui ci si aspettava che succedesse. Ecco dove può aiutare xunit.net.

Con xUnit avete Assert.Throws, in modo da poter fare le cose in questo modo:

[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] è l'equivalente di xUnit [TestMethod]

+0

Se è necessario utilizzare MSTest (che sono costretto spesso dai datori di lavoro), vedere la mia risposta di seguito. – nashwan

9

In alternativa mediante l'attributo ExpectedException, a volte mi definisco due metodi utili per le mie classi di test:

AssertThrowsException() prende un delegato e afferma che getta l'eccezione prevista con il previsto m essaggio.

AssertDoesNotThrowException() prende lo stesso delegato e afferma che non genera un'eccezione.

Questo abbinamento può essere molto utile quando si desidera verificare che venga generata un'eccezione in un caso, ma non nell'altro.

Il loro utilizzo il mio codice di prova di unità potrebbe essere simile a questo:

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

bello e pulito eh?

mio AssertThrowsException() e AssertDoesNotThrowException() metodi sono definiti in una classe base comune come segue:

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

Sono nuovo qui e non ho la reputazione di commentare o downvote, ma ha voluto sottolineare un difetto nell'esempio in Andy White's reply:

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

in tutti i framework di test di unità sono a conoscenza, Assert.Fail opere di un'eccezione, quindi la cattura generica sarà effettivamente mascherare il fallimento del test. Se SomethingThatCausesAnException() non viene lanciato, lo sarà il Assert.Fail, ma non verrà mai rilasciato al test runner per indicare un errore.

Se è necessario rilevare l'eccezione prevista (vale a dire, per asserire determinati dettagli, come il messaggio/le proprietà sull'eccezione), è importante rilevare il tipo specifico previsto e non la classe di eccezione di base. Ciò consentirebbe all'eccezione Assert.Fail di emergere (presupponendo che non si stia generando lo stesso tipo di eccezione del framework di testing dell'unità), ma si permetta comunque la convalida dell'eccezione generata dal metodo SomethingThatCausesAnException().

12

Al v 2.5, NUnit ha il seguente livello metodo Assert s per testare le eccezioni:

Assert.Throws, che verificare un esatto tipo di eccezione:

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

E Assert.Catch, che verificare un eccezione di un determinato tipo o un tipo di eccezione derivato da questo tipo:

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

Come parte, durante il debug di test di unità che generano eccezioni, è possibile evitare VS da breaking on the exception.

Modifica

tanto per fare un esempio di commento di Matteo di seguito, il ritorno del generico Assert.Throws e Assert.Catch è l'eccezione con il tipo di eccezione, che si può quindi esaminare per ulteriori controlli:

// The type of ex is that of the generic type parameter (SqlException) 
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks()); 
Assert.AreEqual(1205, ex.Number); 
+1

Roy Osherove lo consiglia in The Art of Unit Testing, secondo edito, sezione 2.6.2. – Avi

+2

Mi piace 'Assert.Throws', inoltre restituisce l'eccezione in modo da poter scrivere ulteriori asserzioni sull'eccezione stessa. – Matthew

+0

La domanda era per MSTest non NUnit. – nashwan

8

Purtroppo MSTest ha ancora veramente solo l'attributo ExpectedException (solo mostra quanto MS preoccupa MSTest), che IMO è abbastanza terribile perché rompe l'/ Act/modello Disporre affermare e doesnt consentono di specificare exac su quale riga di codice ci si aspetta che si verifichi l'eccezione.

Quando sto usando (/ costretto da un client) per usare MSTest Io uso sempre questa classe helper:

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

Esempio di utilizzo:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null)); 
6

Ora, 2017, è possibile fanno più facile con il nuovo MSTest V2 Framework:

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError()); 
Problemi correlati