Как я могу использовать Assert, чтобы проверить, что было сгенерировано исключение? - PullRequest
751 голосов
/ 01 июня 2009

Как мне использовать Assert (или другой класс Test?), Чтобы проверить, что было сгенерировано исключение?

Ответы [ 22 ]

7 голосов
/ 01 ноября 2018

Вы можете достичь этого с помощью простой однострочной.

Если ваша операция foo.bar() является асинхронной:

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());

Если foo.bar() не является асинхронным

Assert.ThrowsException<Exception>(() => foo.bar());
5 голосов
/ 01 августа 2014

Я не рекомендую использовать атрибут ExpectedException (поскольку он слишком ограничен и подвержен ошибкам) ​​или записывать блок try / catch в каждом тесте (поскольку он слишком сложен и подвержен ошибкам). Используйте хорошо разработанный метод assert - либо предоставленный вашей тестовой средой, либо напишите свой собственный. Вот что я написал и использую.

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

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

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

ПРИМЕЧАНИЯ

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

В отличие от других, я использую «распространения» вместо «бросков», поскольку мы можем только проверить, распространяется ли исключение из вызова. Мы не можем напрямую проверить, что выброшено исключение. Но я полагаю, вы могли бы бросать изображения, чтобы означать: брошенный и не пойман.

ЗАКЛЮЧИТЕЛЬНАЯ МЫСЛЬ

Прежде чем перейти к такому подходу, я рассмотрел использование атрибута ExpectedException, когда тест проверял только тип исключения, и использование блока try / catch, если требовалась дополнительная проверка. Но я не только должен был думать о том, какую технику использовать для каждого теста, но и менять код с одной методики на другую по мере изменения потребностей не было тривиальной задачей. Использование одного последовательного подхода экономит умственные усилия.

Итак, в общем, этот подход к спорту: простота использования, гибкость и надежность (трудно сделать это неправильно).

4 голосов
/ 14 декабря 2016

В качестве альтернативы вы можете попробовать протестировать исключения, которые фактически вызываются следующими двумя строками в вашем тесте.

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
4 голосов
/ 05 апреля 2013

Помощник, предоставленный @Richiban выше, прекрасно работает, за исключением того, что он не обрабатывает ситуацию, когда выдается исключение, но не ожидаемый тип. Следующие адреса, которые:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        /// <summary>
        /// Helper for Asserting that a function throws an exception of a particular type.
        /// </summary>
        public static void Throws<T>( Action func ) where T : Exception
        {
            Exception exceptionOther = null;
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }
            catch (Exception e) {
                exceptionOther = e;
            }

            if ( !exceptionThrown )
            {
                if (exceptionOther != null) {
                    throw new AssertFailedException(
                        String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                        exceptionOther
                        );
                }

                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                    );
            }
        }
    }
}
3 голосов
/ 05 февраля 2016

Поскольку вы упоминаете об использовании других тестовых классов, лучшим вариантом, чем атрибут ExpectedException, является использование Shoudly '* Should.Throw .

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

Допустим, у нас есть требование, чтобы у клиента был адрес , чтобы создать заказ . Если нет, метод CreateOrderForCustomer должен привести к ArgumentException. Тогда мы могли бы написать:

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

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

Обратите внимание, что есть также Should.ThrowAsync для тестирования асинхронных методов.

3 голосов
/ 15 сентября 2012

Ну, я в значительной степени подведу итог тому, что все здесь говорили раньше ... В любом случае, вот код, который я построил в соответствии с хорошими ответами :) Осталось только скопировать и использовать ...

/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
    try
    {
        methodToExecute();
    }
    catch (TException) {
        return;
    }  
    catch (System.Exception ex)
    {
        Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
    }
    Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
}
3 голосов
/ 27 июня 2017

Во встроенном модульном тестировании VS, если вы просто хотите убедиться, что выбрано «любое исключение», но не знаете тип, вы можете использовать перехват всех:

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}
2 голосов
/ 05 мая 2017

В случае использования NUnit , попробуйте это:

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
2 голосов
/ 01 июня 2009

Это будет зависеть от того, какой тестовый фреймворк вы используете?

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

[ExpectedException(typeof(ArgumentException))]
2 голосов
/ 01 июня 2009

Проверьте nUnit Docs для примеров о:

[ExpectedException( typeof( ArgumentException ) )]
...