Лучший способ проверить исключения с помощью Assert, чтобы убедиться, что они будут выброшены - PullRequest
79 голосов
/ 12 апреля 2009

Как вы думаете, это хороший способ проверки исключений? Есть предложения?

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

Assert.IsNotNull(exception);

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

Ответы [ 9 ]

125 голосов
/ 12 апреля 2009

У меня есть несколько разных шаблонов, которые я использую. Я использую атрибут 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 )
        );
    }
}
25 голосов
/ 07 мая 2017

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

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());
17 голосов
/ 12 апреля 2009

Я новичок здесь и не имею репутации комментировать или понижать голос, но хотел указать на недостаток в примере в Ответ Энди Уайта :

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

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

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

15 голосов
/ 25 октября 2013

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

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);
11 голосов
/ 03 октября 2016

К сожалению, 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));
9 голосов
/ 12 апреля 2009

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

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

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

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

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

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

Хорошо и аккуратно, да?

Методы

My 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);
    }
}
4 голосов
/ 12 апреля 2009

Пометьте тест с помощью ExpectedExceptionAttribute (это термин в NUnit или MSTest; пользователям других платформ модульного тестирования может потребоваться перевод).

3 голосов
/ 12 апреля 2009

В большинстве каркасов модульного тестирования .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));
    }

[Факт] является эквивалентом [TestMethod]

0 голосов
/ 17 октября 2018

Рекомендуется использовать синтаксис чистого делегата NUnit .

Пример для тестирования 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>);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...