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

StackOverflow https://stackoverflow.com/questions/933613

Вопрос

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

Это было полезно?

Решение

Похоже, что для «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");
}

Другие советы

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

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

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

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

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

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}

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

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

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

Во-первых, мне не нравится атрибут 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());            
}

ХелперМетод

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

Неплохо, не так ли ;)

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

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

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

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

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

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

Инструкции высокого уровня:скачать сборку и наследовать от Базовый тест и вы можете использовать Утверждать.Выбросы() синтаксис.

Основной метод реализации 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

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

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

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

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

Assert.ThrowsException<Exception>(() => foo.bar());

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

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

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

Помощник, предоставленный @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))
                    );
            }
        }
    }
}

Поскольку вы упоминаете использование других тестовых классов, это лучший вариант, чем ExpectedException атрибут заключается в использовании Шумно's Должен. Бросок.

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 для тестирования асинхронных методов.

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

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

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

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

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

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

Проверить Документация nUnit например о:

[ExpectedException( typeof( ArgumentException ) )]

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

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

[ExpectedException(typeof(ArgumentException))]

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

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

Несмотря на то, что это старый вопрос, я хотел бы добавить к обсуждению новую мысль.Я расширил шаблон «Упорядочить, Действуйте, Утвердить» до «Ожидаемое, Упорядочить, Действуйте, Утверждать».Вы можете создать указатель ожидаемого исключения, а затем утверждать, что он был назначен.Это кажется чище, чем выполнение Asserts в блоке 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);
}

Есть замечательная библиотека под названием NFСвободно говорящий который ускоряет и упрощает процесс написания утверждений.

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

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top