Лучший способ проверить исключения с Assert, чтобы они были брошены

Считаете ли вы, что это хороший способ тестирования исключений? Любые предложения?

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

Assert.IsNotNull(exception);

Я использую MS Test.

Ответы

Ответ 1

У меня есть несколько разных шаблонов, которые я использую. Я использую атрибут ExpectedException большую часть времени, когда ожидается исключение. Однако этого достаточно для большинства случаев, однако есть некоторые случаи, когда этого недостаточно. Исключение может не быть увлекательным - поскольку оно выбрасывается методом, вызываемым отражением, или, возможно, я просто хочу проверить, что другие условия сохранены, скажем, транзакция откат или какое-то значение еще установлено. В этих случаях я обертываю его в блок try/catch, который ожидает точное исключение, делает Assert.Fail, если код преуспевает, а также ловит общие исключения, чтобы убедиться, что другое исключение не выбрано.

Первый случай:

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

Второй случай:

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

Ответ 2

Теперь, 2017 год, вы можете сделать это проще с помощью новой MSTest V2 Framework:

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

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

Ответ 3

Я новичок здесь и не имею репутации для комментариев или downvote, но хотел указать на недостаток в примере в Andy White answer:

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

Во всех модулях модульного тестирования, с которыми я знаком, Assert.Fail работает, бросая исключение, поэтому общий catch фактически замаскирует отказ теста. Если SomethingThatCausesAnException() не выбрасывает, Assert.Fail будет, но это никогда не выскочит на тестовый бегун, чтобы показать сбой.

Если вам нужно поймать ожидаемое исключение (например, для утверждения определенных деталей, таких как сообщение/свойства для исключения), важно уловить определенный ожидаемый тип, а не базовый класс исключений. Это позволило бы исключить исключение Assert.Fail (при условии, что вы не выбрасываете тот же тип исключения, который существует в вашей инфраструктуре тестирования модулей), но все же допускают проверку исключения, которое было создано вашим методом SomethingThatCausesAnException().

Ответ 4

Начиная с v 2.5, NUnit имеет следующий уровень метода Assert для тестирования исключений:

Assert.Throws, который будет проверять для точного типа исключения:

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

И Assert.Catch, который будет проверять исключение определенного типа или тип исключения, производный от этого типа:

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

Кроме того, при отладке модульных тестов, которые выдают исключения, вы можете предотвратить взлом VS для исключения.

Изменить

Просто чтобы привести пример комментария Мэтью ниже, возвращение обобщенных Assert.Throws и Assert.Catch является исключением с типом исключения, которое вы можете затем изучить для дальнейшей проверки:

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

Ответ 5

К сожалению, MSTest STILL действительно имеет только атрибут ExpectedException (только показывает, сколько MS заботится о MSTest), который IMO довольно ужасен, потому что он разбивает шаблон Arrange/Act/Assert, и он не позволяет вам точно указать, какую строку кода вы хотите ожидать исключения.

Когда я использую (/принудительно клиентом) использовать MSTest, я всегда использую этот вспомогательный класс:

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

Пример использования:

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

Ответ 6

В качестве альтернативы использованию атрибута ExpectedException я иногда определяю два полезных метода для своих тестовых классов:

AssertThrowsException() принимает делегата и утверждает, что он генерирует ожидаемое исключение с ожидаемым сообщением.

AssertDoesNotThrowException() принимает тот же делегат и утверждает, что он не генерирует исключение.

Это сопряжение может быть очень полезно, если вы хотите проверить, что исключение выбрано в одном случае, но не другое.

Используя их, мой код unit test может выглядеть так:

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

Приятный и аккуратный ха?

Мои методы AssertThrowsException() и AssertDoesNotThrowException() определены в общем базовом классе следующим образом:

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

Ответ 7

Отметить тест с помощью атрибута ExpectedExceptionAttribute (это термин в NUnit или MSTest, пользователям могут потребоваться переводы других структурных модулей тестирования).

Ответ 8

С большинством .net модульных модулей тестирования вы можете поместить атрибут [ExpectedException] в методе тестирования. Однако это не может сказать вам, что исключение произошло в тот момент, когда вы ожидали этого. Это может помочь xunit.net.

С xunit у вас есть Assert.Throws, поэтому вы можете делать такие вещи:

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

[Факт] - это xunit-эквивалент [TestMethod]

Ответ 9

Предложите использовать NUnit clean синтаксис делегата.

Пример для тестирования 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>);
}