web-dev-qa-db-fra.com

Meilleure façon de tester les exceptions avec Assert pour s'assurer qu'elles seront levées

Pensez-vous que c'est un bon moyen de tester les exceptions? Aucune suggestion?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);

J'utilise MS Test.

82
Hannoun Yassir

J'ai plusieurs modèles que j'utilise. J'utilise l'attribut ExpectedException la plupart du temps lorsqu'une exception est attendue. Cela suffit dans la plupart des cas, cependant, dans certains cas, cela ne suffit pas. L'exception peut ne pas être capturable - car elle est renvoyée par une méthode qui est invoquée par réflexion - ou peut-être veux-je simplement vérifier que d'autres conditions sont remplies, par exemple, une transaction est annulée ou une valeur a toujours été définie. Dans ces cas, je l'enveloppe dans un bloc try/catch Qui attend l'exception exacte, effectue un Assert.Fail Si le code réussit et attrape également les exceptions génériques pour s'assurer qu'une exception différente n'est pas levée.

Premier cas:

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

Deuxième cas:

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

Maintenant, 2017, vous pouvez le faire plus facilement avec le nouveau MSTest V2 Framework :

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

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);
30
Icaro Bombonato

Je suis nouveau ici et je n'ai pas la réputation de commenter ou de voter, mais je voulais signaler un défaut dans l'exemple de réponse de Andy White :

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

Dans tous les frameworks de tests unitaires que je connais bien, Assert.Fail Fonctionne en lançant une exception. Le verrou générique masquera donc l'échec du test. Si SomethingThatCausesAnException() ne lance pas, le Assert.Fail Le fera, mais cela ne sera jamais transmis au testeur pour indiquer un échec.

Si vous devez intercepter l’exception attendue (c’est-à-dire pour affirmer certains détails, comme le message/les propriétés de l’exception), il est important d’attraper le type attendu spécifique et non la classe d’exception de base. Cela permettrait à l’exception Assert.Fail De jaillir (en supposant que vous ne lançiez pas le même type d’exception que votre framework de test unitaire), tout en permettant la validation de l’exception émise par votre fonction SomethingThatCausesAnException() méthode.

17
allgeek

À partir de v 2.5, NUnit , le niveau de méthode suivant Asserts permet de tester les exceptions:

Assert.Throws , qui testera un type d'exception exact:

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

Et Assert.Catch, qui testera une exception d'un type donné ou un type d'exception dérivé de ce type:

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

Par ailleurs, lors du débogage de tests unitaires générant des exceptions, vous pouvez empêcher les VS de en cas d'exception .

Modifier

Juste pour donner un exemple du commentaire de Matthew ci-dessous, le retour du générique Assert.Throws et Assert.Catch est l'exception avec le type de l'exception, que vous pouvez ensuite examiner pour une inspection ultérieure:

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

Malheureusement, MSTest STILL ne dispose que de l'attribut ExpectedException (il montre simplement à quel point MS se soucie de MSTest). IMO est plutôt horrible car il casse le modèle Arrange/Act/Assert et ne vous permet pas de spécifier la ligne de code que vous attendez de l'exception. se produire sur.

Lorsque j'utilise (/ forcé par un client) d'utiliser MSTest, j'utilise toujours cette classe d'assistance:

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

Exemple d'utilisation:

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

Au lieu d'utiliser l'attribut ExpectedException, je définis parfois deux méthodes utiles pour mes classes de test:

AssertThrowsException() prend un délégué et affirme qu'il lève l'exception attendue avec le message attendu.

AssertDoesNotThrowException() prend le même délégué et affirme qu'il ne lève pas d'exception.

Cette association peut s'avérer très utile lorsque vous souhaitez vérifier qu'une exception est levée dans un cas mais pas dans l'autre.

En les utilisant, mon code de test unitaire pourrait ressembler à ceci:

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

Gentil et bien hein?

Mes méthodes AssertThrowsException() et AssertDoesNotThrowException() sont définies sur une classe de base commune comme suit:

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);
    }
}
9
GrahamS

Marquez le test avec ExpectedExceptionAttribute (c'est le terme utilisé dans NUnit ou MSTest; les utilisateurs d'autres infrastructures de test d'unité peuvent nécessiter une traduction).

4
itowlson

Avec la plupart des frameworks de tests unitaires .net, vous pouvez placer un attribut [ExpectedException] sur la méthode de test. Cependant, cela ne peut pas vous dire que l'exception s'est produite au moment où vous vous en attendiez. C'est là que xunit.net peut vous aider.

Avec xunit vous avez Assert.Throws, vous pouvez donc faire les choses suivantes:

    [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] est l'équivalent xunit de [TestMethod].

3
Steve Willcock

Suggérez d’utiliser la syntaxe des délégués propres de NUnit .

Exemple de test ArgumentNullExeption:

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}
0
Shahar Shokrani