it-swarm.com.ru

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

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

715
Alex

По-видимому, для «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, "[email protected]");
}
854
Kevin Pullin

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

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

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

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

103
Richiban

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

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

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

44
damir

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

35
jrista

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

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

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

24
Glenn

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

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

Вы можете загрузить пакет из 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

13
Bradley Braithwaite

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

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

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

12
Martin Beeby

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

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

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

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

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

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

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

5
steve

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

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

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

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

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

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

/// <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
roeiba

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

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

Допустим, у нас есть требование, чтобы customer должен иметь address для создания order. Если нет, метод 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
Justin J Stark

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

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

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

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

[ExpectedException(typeof(ArgumentException))]
2
Jay

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

[ExpectedException( typeof( ArgumentException ) )]
2
Jon Masters

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

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

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

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