Как я могу использовать
Assert
(или другой класс Test?), Чтобы убедиться, что возникло исключение?
Как я могу использовать
Assert
(или другой класс Test?), Чтобы убедиться, что возникло исключение?
Ответы:
Для «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
}
Если вам абсолютно необходимо перехватить Exception, вам нужно сбросить 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());
}
Который выглядит и ведет себя намного больше, чем остальные синтаксисы вашего модульного теста.
Assert.ThrowsException<T>
и Assert.ThrowsExceptionAsync<T>
- см blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
если вы используете NUNIT, вы можете сделать что-то вроде этого:
Assert.Throws<ExpectedException>(() => methodToTest());
Также возможно сохранить выброшенное исключение для дальнейшей его проверки:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Если вы используете MSTest, который изначально не имел ExpectedException
атрибута, вы можете сделать это:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Будьте осторожны с использованием 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/
MSTest (v2) теперь имеет функцию Assert.ThrowsException, которую можно использовать так:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Вы можете установить его с помощью nuget: Install-Package MSTest.TestFramework
В проекте, над которым я работаю, у нас есть другое решение, которое делает это.
Во-первых, мне не нравится атрибут 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;
}
Аккуратно, не правда ли;)
Это атрибут метода тестирования ... вы не используете Assert. Выглядит так:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Вы можете загрузить пакет из 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
Вы можете достичь этого с помощью одной строки.
Если ваша операция foo.bar()
асинхронная:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Если foo.bar()
не асинхронный
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
например. Старый Try Catch и проверка ответа на исключение по-прежнему предпочтительнее, если у вас есть расширенные критерии для тестирования, но для многих моих случаев это очень помогает!
Я не рекомендую использовать атрибут 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, если требовалась дополнительная проверка. Но я не только должен был думать о том, какую технику использовать для каждого теста, но и менять код с одной методики на другую по мере изменения потребностей не было тривиальной задачей. Использование одного последовательного подхода экономит умственные усилия.
Таким образом, в целом, этот подход спортивный: простота использования, гибкость и надежность (трудно сделать это неправильно).
Помощник, предоставленный @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
атрибут, является использование 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
для тестирования асинхронных методов.
Ну, я подведу итог тому, что все здесь говорили раньше ... В любом случае, вот код, который я построил в соответствии с хорошими ответами :) Осталось только скопировать и использовать ...
/// <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.");
}
Проверьте nUnit Docs для примеров о:
[ExpectedException( typeof( ArgumentException ) )]
В случае использования NUnit , попробуйте это:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Существует потрясающая библиотека под названием NFluent, которая ускоряет и облегчает способ написания ваших утверждений .
Довольно просто написать утверждение для выброса исключения:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Хотя это старый вопрос, я хотел бы добавить новую мысль к обсуждению. Я расширил образец «Организовать, Действовать, Утвердить», «Ожидать», «Организовать, Действовать, Утвердить». Вы можете сделать указатель ожидаемого исключения, а затем утверждать, что он был назначен. Это кажется более чистым, чем выполнение ваших утверждений в блоке 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);
}