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

740

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

Показать ещё 4 комментария
Теги:
mstest
unit-testing
vs-unit-testing-framework
assert

22 ответа

843
Лучший ответ

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

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

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
  • 23
    Приведенный выше атрибут ExpectedException также работает в NUnit (но [TestMethod] должен быть [Test]).
  • 5
    @dbkk: Не работает точно так же в NUnit - сообщение обрабатывается как строка, которая должна соответствовать сообщению об исключении (и IU считает, что это имеет больше смысла)
Показать ещё 6 комментариев
217

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

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
}

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

catch (AssertionException) { throw; }

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

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}
  • 19
    +1, я использую этот способ вместо атрибута, когда мне нужно сделать утверждения, выходящие за рамки только типа исключения. Например, что если нужно проверить, что определенные поля в экземпляре исключения установлены на определенные значения.
  • 0
    Мне это нравится, потому что вам не нужно указывать точное сообщение об ошибке, как при использовании атрибутов
Показать ещё 7 комментариев
100

Мой предпочтительный метод для реализации этого - написать метод под названием 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))
                    );
            }
        }
    }
}

Это означает, что ваш unit test выглядит так:

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

Что выглядит и ведет себя намного больше, чем остальные синтаксисы unit test.

  • 0
    так тебе не нравятся атрибуты?
  • 1
    Избавьтесь от флага bool и поместите бросок на строку сразу после вызова для более компактной реализации.
Показать ещё 5 комментариев
61

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

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

если вы используете 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

  • 0
    Это также работает с MSTest.
36

Будьте осторожны с использованием 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/

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

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

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

Я делаю это с помощью helpermethod.

Test

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

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

15

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

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
13

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

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

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

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

  • 0
    Спасибо за пример. У вас есть пример того, как проверить Assert.DoesNotThrow () или эквивалент?
11

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

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

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

  • 0
    В 2018 году это считается наилучшей практикой, поскольку проверяется только выбрасываемый тестируемый модуль, а не какой-либо другой код.
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)
    }

ПРИМЕЧАНИЯ

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

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

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

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

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

4

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

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

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

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

Assert.ThrowsException<Exception>(() => foo.bar());
  • 1
    Есть много других ответов, для меня я искал краткий способ проверки на известные условия сбоя только по типу исключения, это облегчает чтение тестовых случаев. ПРИМЕЧАНИЕ. Тип исключения не совпадает с унаследованными классами исключений, такими как стандартный try-catch, поэтому приведенный выше пример не будет перехватывать ArgumentException например. Старый 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.
  • 0
    @Martin Я бы удалил код, включающий исключение-другое, и просто перебросил бы из второго предложения catch
3

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

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
3

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

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

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

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

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}
2

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

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
2

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

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

[ExpectedException(typeof(ArgumentException))]
2

Обратите внимание на nUnit Docs для примеров о:

[ExpectedException( typeof( ArgumentException ) )]
1

Даже если это старый вопрос, я хотел бы добавить новую мысль к обсуждению. Я продлил шаблон Arrange, Act, Assert, который должен быть ожидаемым, упорядочить, действовать, утверждать. Вы можете сделать ожидаемый указатель исключения, а затем утверждать, что ему присвоен. Это кажется более чистым, чем выполнение ваших утверждений в блоке 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);
}
0

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

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

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }

Ещё вопросы

Сообщество Overcoder
Наверх
Меню