Як я можу використовувати
Assert
(чи інший тестовий клас?), Щоб перевірити, чи був викинутий виняток?
Як я можу використовувати
Assert
(чи інший тестовий клас?), Щоб перевірити, чи був викинутий виняток?
Відповіді:
Для "Тестування Visual Studio Team" з'являється, ви застосуєте атрибут ExpectedException до методу тесту.
Зразок з документації тут: Програма для тестування підрозділу з тестуванням команди Visual Studio
[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();
}
Мій кращий метод для здійснення цього полягає в тому, щоб написати метод, який називається Throw, і використовувати його так само, як і будь-який інший метод 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);
}
Будьте обережні при використанні очікуваного ексцепції, оскільки це може призвести до декількох підводних каменів, як показано тут:
http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx
І ось:
http://xunit.github.io/docs/comparisons.html
Якщо вам потрібно перевірити на винятки, способи менш спохмурні. Ви можете скористатися методом спробу {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
У проекті, над яким я працюю, у нас є ще одне рішення.
По-перше, мені не подобається, що очікуване ExxceptionAttribute має враховувати, який виклик методу викликав Виняток.
Я роблю це замість цього за допомогою helpermethod.
Тест
[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 () .
Основний метод реалізації 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
Домогтися цього можна простою однолінійкою.
Якщо ваша операція foo.bar()
асинхронізована:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Якщо foo.bar()
це не асинхронізація
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
приклад. Старий спробу Catch та тестування відповіді на виключення все ще вдається, якщо у вас є розширені критерії для тестування, але для багатьох моїх випадків це дуже допомагає!
Я не рекомендую використовувати атрибут 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)
}
ПРИМІТКИ
Повернення винятку замість підтримки зворотного виклику перевірки є розумною ідеєю, за винятком того, що це робить синтаксис виклику цього твердження дуже іншим, ніж інші твердження, які я використовую.
На відміну від інших, я використовую "propagates" замість "кидок", оскільки ми можемо перевірити, чи поширюється виняток з виклику. Ми не можемо перевірити безпосередньо, чи є виняток. Але я припускаю, що ви могли б зобразити кидки, щоб означати: кинуті та не спіймані.
Фінальна думка
Перш ніж перейти на такий тип підходу, я розглядав можливість використання атрибуту 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
тестування асинхронних методів.
У тестуванні вбудованого модуля VS, якщо ви просто хочете перевірити, що "будь-який виняток" викинуто, але ви не знаєте типу, ви можете використовувати спіймання всіх:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
Ну я майже підсумую те, що всі тут казали раніше ... У будь-якому випадку, ось код, який я створив відповідно до хороших відповідей :) Все, що потрібно зробити, - це скопіювати та використовувати ...
/// <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 на приклади:
[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");
}
Хоча це старе питання, я хотів би додати нову думку до дискусії. Я розширив шаблон очікування «Упорядкувати», «Акт», «Оцінити», «Упорядкувати», «Акт», «Затвердити». Ви можете зробити вказівник очікуваного винятку, а потім стверджувати, що йому призначено. Це відчуває себе чистішим, ніж робити ваші акти в блоці вилову, залишаючи розділ "Акт" здебільшого лише для одного рядка коду для виклику методу, який перевіряється. Вам також не потрібно 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);
}