Pregunta La mejor forma de probar excepciones con Assert para garantizar que se lanzarán


¿Crees que esta es una buena forma de probar excepciones? ¿Alguna sugerencia?

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

Assert.IsNotNull(exception);

Estoy usando MS Test.


76
2018-04-12 00:12


origen


Respuestas:


Tengo un par de patrones diferentes que uso. Yo uso el ExpectedException atribuir la mayor parte del tiempo cuando se espera una excepción. Esto es suficiente para la mayoría de los casos, sin embargo, hay algunos casos en que esto no es suficiente. Es posible que la excepción no sea atrapable, ya que es lanzada por un método invocado por reflexión, o tal vez solo quiero verificar que se cumplan otras condiciones, digamos que una transacción se revierte o aún se ha establecido algún valor. En estos casos, lo envuelvo en una try/catch bloque que espera la excepción exacta, hace un Assert.Fail si el código tiene éxito y también detecta excepciones genéricas para asegurarse de que no se lanza una excepción diferente.

Primer caso:

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

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

118
2018-04-12 00:40



Soy nuevo aquí y no tengo la reputación de comentar o de votar negativamente, pero quería señalar un error en el ejemplo de La respuesta de Andy White:

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

En todos los marcos de pruebas de unidades con los que estoy familiarizado, Assert.Fail funciona lanzando una excepción, por lo que la captura genérica realmente enmascarará la falla de la prueba. Si SomethingThatCausesAnException() no arroja, el Assert.Fail lo hará, pero eso nunca pasará al corredor de prueba para indicar la falla.

Si necesita detectar la excepción esperada (es decir, para afirmar ciertos detalles, como el mensaje / las propiedades de la excepción), es importante detectar el tipo específico esperado, y no la clase Excepción base. Eso permitiría Assert.Fail excepción a bubble out (suponiendo que no está lanzando el mismo tipo de excepción que el marco de prueba de su unidad), pero aún así permita la validación de la excepción lanzada por su SomethingThatCausesAnException() método.


16
2018-04-12 19:01



Ahora, 2017, puedes hacerlo más fácil con el nuevo MSTest V2 Framework:

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

12
2018-05-07 00:19



A partir de v 2.5, NUnit tiene el siguiente nivel de método Asserts para probar excepciones:

Assert.Throws, que probará un tipo de excepción exacto:

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

Y Assert.Catch, que probará una excepción de un tipo determinado o un tipo de excepción derivado de este tipo:

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

Como un lado, al depurar las pruebas unitarias que arrojan excepciones, es posible que desee evitar VS rompiendo la excepción.

Editar

Solo para dar un ejemplo del comentario de Matthew a continuación, la devolución del genérico Assert.Throws y Assert.Catch es la excepción con el tipo de la excepción, que luego puede examinar para una inspección adicional:

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

11
2017-10-25 11:33



Desafortunadamente MSTest STILL solo tiene realmente el atributo ExpectedException (solo muestra cuánto le importa MS a MSTest) que IMO es bastante horrible porque rompe el patrón Arrange / Act / Assert y no le permite especificar exactamente qué línea de código espera la excepción para ocurrir en.

Cuando estoy usando (/ forzado por un cliente) para usar MSTest siempre utilizo esta clase de ayuda:

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

Ejemplo de uso:

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

10
2017-10-03 14:10



Como una alternativa al uso ExpectedExceptionatributo, a veces defino dos métodos útiles para mis clases de prueba:

AssertThrowsException() toma un delegado y afirma que arroja la excepción esperada con el mensaje esperado.

AssertDoesNotThrowException() toma el mismo delegado y afirma que no lanza una excepción.

Este emparejamiento puede ser muy útil cuando quiere probar que se arroja una excepción en un caso, pero no en el otro.

Utilizándolos, mi código de prueba de unidad podría verse así:

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

Agradable y aseado ¿eh?

Mi AssertThrowsException() y AssertDoesNotThrowException() los métodos se definen en una clase base común de la siguiente manera:

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
2018-04-12 18:51



Marque la prueba con ExpectedExceptionAttribute (este es el término en NUnit o MSTest; es posible que los usuarios de otros marcos de pruebas de unidades necesiten traducir).


4
2018-04-12 00:17



Con la mayoría de los frameworks de pruebas de unidades .net puede poner un atributo [ExpectedException] en el método de prueba. Sin embargo, esto no puede decirle que la excepción ocurrió en el momento en que usted lo esperaba. Ahí es donde xunit.net poder ayudar.

Con xunit tienes Assert.Throws, entonces puedes hacer cosas como esta:

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

[Hecho] es el equivalente de xunit de [TestMethod]


2
2018-04-12 00:17