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

830

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

Alex
источник
Какую систему модульного тестирования вы используете?
Кевин Пуллин
3
Visual Studio Integrated
Alex
4
Не помогает атрибут ExpectedException? ссылка: msdn.microsoft.com/en-us/library/…
шахкалпеш
2
Забавно, я только что закончил искать ответ на этот вопрос и нашел его по адресу stackoverflow.com/questions/741029/testing-exceptions .
dfjacobs
Также смотрите: stackoverflow.com/questions/741029/…
bytedev

Ответы:

978

Для «Visual Studio Team Test» кажется, что вы применяете атрибут ExpectedException к методу теста.

Пример из документации здесь: пошаговое руководство по тестированию модулей с помощью Visual Studio Team Test

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
Кевин Пуллин
источник
25
Приведенный выше атрибут ExpectedException также работает в NUnit (но [TestMethod] должен быть [Test]).
dbkk
5
@dbkk: Не работает точно так же в NUnit - сообщение обрабатывается как строка, которая должна соответствовать сообщению об исключении (а IU считает, что это имеет больше смысла)
Рубен Бартелинк
29
Этот атрибут выполняет работу и является встроенной функцией для программистов на c #, но я не рекомендую использовать его, поскольку он недостаточно гибок. Подумайте, что произойдет, если ваш код установки теста выдает тип исключения: тест проходит успешно, но на самом деле не выполняет то, что вы ожидали. Или что, если вы хотите проверить состояние объекта исключения. Я обычно хочу использовать StringAssert.Contains (e.Message ...), а не тестировать все сообщение. Используйте метод assert, как описано в других ответах.
Стив
3
Избегайте использования ExpectedException в NUnit, так как он будет отброшен в NUnit 3.0. Я предпочитаю использовать Assert.Throws <SpecificException> ()
Теренс
5
Вы можете использовать Assert.ThrowsException <T> и Assert.ThrowsExceptionAsync <T> в MsTest.
Гопал Кришнан
257

Обычно ваш тестовый фреймворк будет иметь ответ на этот вопрос. Но если он недостаточно гибок, вы всегда можете сделать это:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

Как указывает @Jonas, это НЕ работает для отлова базового исключения:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

Если вам абсолютно необходимо перехватить Exception, вам нужно сбросить Assert.Fail (). Но на самом деле, это признак того, что вы не должны писать это от руки; проверьте свои тестовые рамки на наличие вариантов или посмотрите, можете ли вы выдать более значимое исключение для проверки.

catch (AssertionException) { throw; }

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

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}
ojrac
источник
20
+1, я использую этот способ вместо атрибута, когда мне нужно сделать утверждения, выходящие за рамки только типа исключения. Например, что делать, если нужно проверить, что для определенных полей в экземпляре исключения установлены определенные значения.
Павел Репин
2
Вы не обязаны указывать сообщение об ошибке. Этого достаточно: [ExpectedException (typeof (ArgumentException))]
mibollma
5
Я думаю, что это решение является лучшим. [ExpectedException (typeof (ArgumentException))] имеет свои применения, если тест прост, но, на мой взгляд, это ленивое решение, и его неудобство может привести к подводным камням. Это решение дает вам конкретный контроль для проведения более правильного теста, а также вы можете сделать тест Writeline в своем отчете о выполнении теста, чтобы исключение действительно было выдано, как и ожидалось.
злая рыба
12
Будьте осторожны с этим, потому что Assert.Fail () вызывает исключение, если вы его ловите, проход теста!
Джонас
4
@ Vinnyq12 Я имею в виду, что первый тест в приведенном выше примере никогда не будет неудачным. Тест не пройден, если выдается исключение (а не «catch» с помощью ExpectedExceptionAttribute)
Jonas
113

Мой предпочтительный метод для реализации этого - написать метод Throws и использовать его, как и любой другой метод Assert. К сожалению, .NET не позволяет вам писать метод статического расширения, поэтому вы не можете использовать этот метод, как если бы он действительно принадлежал сборке в классе Assert; просто сделайте другой под названием MyAssert или что-то подобное. Класс выглядит так:

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

namespace YourProject.Tests
{
    public static class MyAssert
    {
        public static void Throws<T>( Action func ) where T : Exception
        {
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }

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

Это означает, что ваш юнит-тест выглядит так:

[TestMethod()]
public void ExceptionTest()
{
    String testStr = null;
    MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}

Который выглядит и ведет себя намного больше, чем остальные синтаксисы вашего модульного теста.

Richiban
источник
1
Избавьтесь от флага bool и поместите бросок на строку сразу после вызова для более компактной реализации.
GT
11
Единственное, что делает это лучше, - это то, что функция возвращает пойманное исключение, чтобы вы могли продолжать утверждать, что такие вещи, как атрибуты исключения, являются правильными.
Марк Хилдрет
2
Спасибо! Мне кажется, это лучший подход, потому что это короткий способ проверить несколько исключений в одном методе. Это также намного более читабельно.
Дэвид Шеррет
2
Атрибуты @MickeyPerlstein нарушают правила AAA для тестирования. В частности, если ваше Соглашение сгенерировало исключение еще до того, как вы дойдете до Акта, тогда ваш тест пройден ... еек!
Freedn-M
2
Microsoft наконец -то получил раунд обновления MSTest - v2 поддерживает Assert.ThrowsException<T>и Assert.ThrowsExceptionAsync<T>- см blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
Quango
62

если вы используете NUNIT, вы можете сделать что-то вроде этого:

Assert.Throws<ExpectedException>(() => methodToTest());


Также возможно сохранить выброшенное исключение для дальнейшей его проверки:

ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);

Смотрите: http://nunit.org/docs/2.5/exceptionAsserts.html

Дамир
источник
60

Если вы используете MSTest, который изначально не имел ExpectedExceptionатрибута, вы можете сделать это:

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}
Джон Лимджап
источник
3
Это работает, но я не рекомендую это вообще, так как логика слишком сложна. Не сказать, что это запутанно, но подумайте, если вы напишите этот блок кода для нескольких тестов - 10, 100 тестов. Эта логика должна быть разработана для хорошо разработанного метода подтверждения. Смотрите другие ответы.
Стив
35

Будьте осторожны с использованием ExpectedException, так как это может привести к нескольким подводным камням, как показано здесь:

http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx

И здесь:

http://xunit.github.io/docs/comparisons.html

Если вам нужно проверить исключения, есть и другие, которые не одобряются. Вы можете использовать метод try {act / fail} catch {assert}, который может быть полезен для платформ, которые не имеют прямой поддержки тестов исключений, кроме ExpectedException.

Лучшей альтернативой является использование xUnit.NET, которая является очень современной, ориентированной на будущее и расширяемой структурой модульного тестирования, которая извлекла уроки из всех других ошибок и усовершенствована. Одним из таких улучшений является Assert.Throws, который обеспечивает гораздо лучший синтаксис для утверждения исключений.

Вы можете найти xUnit.NET на github: http://xunit.github.io/

jrista
источник
4
Обратите внимание, что NUnit 2.5 также теперь поддерживает синтаксис стиля Assert.Throws
nunit.com/index.php?p=releaseNotes&r=2.5
То, что юнит-тесты останавливаются, чтобы вы знали об исключении при использовании ExpectedException, сводит меня с ума. Почему MS посчитал хорошей идеей сделать ручной шаг в автоматических тестах? Спасибо за ссылки.
Муравей
@Ant: MS скопировала NUnit ... так что реальный вопрос в том, почему NUnit подумал, что это хорошая идея?
jrista
28

MSTest (v2) теперь имеет функцию Assert.ThrowsException, которую можно использовать так:

Assert.ThrowsException<System.FormatException>(() =>
            {
                Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
            }); 

Вы можете установить его с помощью nuget: Install-Package MSTest.TestFramework

Мартин Биби
источник
В 2018 году это считается наилучшей практикой, поскольку проверяется только выбрасываемый тестируемый модуль, а не какой-либо другой код.
СМ
24

В проекте, над которым я работаю, у нас есть другое решение, которое делает это.

Во-первых, мне не нравится атрибут ExpectedExceptionAttribute, потому что он принимает во внимание, какой вызов метода вызвал исключение.

Я делаю это с помощью вспомогательного метода вместо этого.

Тестовое задание

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

HelperMethod

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

Аккуратно, не правда ли;)

Гленн
источник
14

Это атрибут метода тестирования ... вы не используете Assert. Выглядит так:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
bytebender
источник
13

Вы можете загрузить пакет из Nuget, используя: PM> Install-Package MSTestExtensions, который добавляет синтаксис Assert.Throws () в стиле nUnit / xUnit к MsTest.

Инструкции высокого уровня: загрузите сборку и наследуйте от BaseTest, и вы можете использовать синтаксис Assert.Throws () .

Основной метод для реализации Throws выглядит следующим образом:

public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
    try
    {
        task();
    }
    catch (Exception ex)
    {
        AssertExceptionType<T>(ex);
        AssertExceptionMessage(ex, expectedMessage, options);
        return;
    }

    if (typeof(T).Equals(new Exception().GetType()))
    {
        Assert.Fail("Expected exception but no exception was thrown.");
    }
    else
    {
        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
    }
}

Раскрытие: я собрал этот пакет.

Дополнительная информация: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html

Брэдли Брейтуэйт
источник
Спасибо за пример. У вас есть пример того, как проверить Assert.DoesNotThrow () или эквивалент?
Лейн Гулсби
10

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

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

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

Если foo.bar()не асинхронный

Assert.ThrowsException<Exception>(() => foo.bar());
Cfrim
источник
1
Есть много других ответов, для меня я искал краткий способ проверки на известные условия сбоя только по типу исключения, это облегчает чтение тестовых случаев. ПРИМЕЧАНИЕ. Тип исключения не совпадает с унаследованными классами исключений, такими как стандартный try-catch, поэтому приведенный выше пример не будет перехватывать, ArgumentExceptionнапример. Старый Try Catch и проверка ответа на исключение по-прежнему предпочтительнее, если у вас есть расширенные критерии для тестирования, но для многих моих случаев это очень помогает!
Крис Шаллер
5

Я не рекомендую использовать атрибут 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

Помощник, предоставленный @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))
                    );
            }
        }
    }
}
Мартин Коннелл
источник
2
Хммм ... Я понимаю идею, но я не уверен, что согласен, что лучше. Тот факт, что мы хотим убедиться, что определенное исключение поднято, не означает, что все остальные должны быть рассмотрены как ошибка утверждения. ИМХО, неизвестное исключение должно просто пузыриться в стеке, как в любой другой операции assert.
Crono
@Martin Я бы удалил код, включающий исключениеДругое, и просто перебросил бы из второго предложения catch
Tom Lint
4

Поскольку вы упоминаете об использовании других тестовых классов, лучшим вариантом, чем ExpectedExceptionатрибут, является использование Shoudly 's 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для тестирования асинхронных методов.

Джастин Джей Старк
источник
4

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

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
Matias
источник
4

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

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}
ТТТ
источник
3

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

/// <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.");  
}
roeiba
источник
2

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

[ExpectedException( typeof( ArgumentException ) )]
Джон Мастерс
источник
2

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

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

[ExpectedException(typeof(ArgumentException))]
сойка
источник
2

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

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Амир Чатрбар
источник
2

Существует потрясающая библиотека под названием NFluent, которая ускоряет и облегчает способ написания ваших утверждений .

Довольно просто написать утверждение для выброса исключения:

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }
mirind4
источник
1

Хотя это старый вопрос, я хотел бы добавить новую мысль к обсуждению. Я расширил образец «Организовать, Действовать, Утвердить», «Ожидать», «Организовать, Действовать, Утвердить». Вы можете сделать указатель ожидаемого исключения, а затем утверждать, что он был назначен. Это кажется более чистым, чем выполнение ваших утверждений в блоке catch, оставляя ваш раздел Act в основном только для одной строки кода для вызова тестируемого метода. Вы также не должны Assert.Fail();или returnиз нескольких точек в коде. Любое другое выброшенное исключение вызовет сбой теста, потому что он не будет перехвачен, и если будет сгенерировано исключение вашего ожидаемого типа, но это было не то, что вы ожидали, Утверждение против сообщения или других свойств исключение поможет убедиться, что ваш тест не пройдет непреднамеренно.

[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
    // Expectations
    InvalidOperationException expectedException = null;
    string expectedExceptionMessage = "Bar did something invalid.";

    // Arrange
    IDependency dependency = DependencyMocks.Create();
    Foo foo = new Foo(dependency);

    // Act
    try
    {
        foo.Bar();
    }
    catch (InvalidOperationException ex)
    {
        expectedException = ex;
    }

    // Assert
    Assert.IsNotNull(expectedException);
    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}
Адам Венеция
источник