Як використати Assert, щоб перевірити, чи було викинуто виняток?


830

Як я можу використовувати Assert(чи інший тестовий клас?), Щоб перевірити, чи був викинутий виняток?


Який блок тестування одиниць ви використовуєте?
Кевін Пуллін

3
Візуальна студія інтегрована
Алекс

4
Чи не допомагає атрибут очікуваного розгляду? ref: msdn.microsoft.com/en-us/library/…
shahkalpesh

2
Смішно, я щойно закінчив шукати відповідь на це, знайшов його на stackoverflow.com/questions/741029/testing-exceptions .
dfjacobs

Також дивіться: stackoverflow.com/questions/741029 / ...
bytedev

Відповіді:


978

Для "Тестування 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");
}

25
Атрибут очікуваного розширення працює і в NUnit (але [TestMethod] має бути [Test]).
dbkk

5
@dbkk: Не працює точно так само в NUnit - повідомлення трактується як рядок, який повинен відповідати повідомленню про виключення (і IU думаю, що це має більше сенсу)
Ruben Bartelink

29
Цей атрибут виконує завдання і є вбудованою функцією для програмістів c #, але я не рекомендую його використовувати, оскільки він недостатньо гнучкий. Поміркуйте, що станеться, якщо тип виключення кине ваш код налаштування тесту: тест проходить, але насправді не зробив те, що ви очікували. Або що робити, якщо ви хочете перевірити стан об’єкта виключення. Зазвичай я хочу використовувати StringAssert.Contains (e.Message ...), а не перевіряти все повідомлення. Використовуйте метод затвердження, як описано в інших відповідях.
steve

3
Уникайте використання ExpectedException в NUnit, оскільки воно буде скинуто на NUnit 3.0. Я вважаю за краще використовувати Assert.Throws <SpecificException> ()
Теренс

5
Ви можете використовувати Assert.ThrowsException <T> і Assert.ThrowsExceptionAsync <T> в межах MsTest.
Гопал Крішнан

257

Зазвичай ваша тестувальна рамка матиме відповідь на це. Але якщо це недостатньо гнучко, ви завжди можете це зробити:

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

20
+1, я використовую цей спосіб замість атрибута, коли мені потрібно робити твердження, що перевищують лише тип виключення. Наприклад, що робити, якщо потрібно перевірити, чи певні поля в екземплярі винятку встановлені на певні значення.
Павло Рєпін

2
Вам не потрібно вказувати повідомлення про помилку. Цього достатньо: [ОчікуванеЕксцепція (typeof (ArgumentException))]
mibollma

5
Я думаю, що це рішення є найкращим. [ОчікуванеЕксцепція (typeof (ArgumentException))] використовує це, якщо тест простий, але, на мій погляд, ліниве рішення і його комфорт може призвести до підводних каменів. Це рішення дає вам специфічний контроль, щоб зробити більш правильний тест, плюс ви можете до тестової Writeline до свого звіту про тестовий пробіг, щоб виняток справді був кинутий, як очікувалося.
злодія

12
Будьте обережні з цим, тому що Assert.Fail () зробить виняток, якщо ви його спіймаєте, тестовий пропуск!
Йонас

4
@ Vinnyq12 Що я маю на увазі, що перший тест у наведеному вище прикладі ніколи не провалиться. Тест не вдасться, якщо виняток буде викинутий (а не "зловити" очікуванимExceptionAttribute)
Йонас

113

Мій кращий метод для здійснення цього полягає в тому, щоб написати метод, який називається 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());
}

Що виглядає і веде себе набагато більше, як і решта ваших синтаксисів тестових одиниць.


1
Позбудьтесь прапора bool і поставте кидок на лінію прямо після виклику для більш компактної реалізації.
gt

11
Єдине, що робить це кращим - це повернути функцію повернутий виняток, щоб ви могли продовжувати стверджувати, що такі речі, як атрибути у винятку, є правильними.
Марк Хілдрет

2
Дякую! Мені це здається найкращим підходом, оскільки це короткий спосіб перевірити декілька винятків одним методом. Це також набагато читабельніше.
Девід Шеррет

2
@MickeyPerlstein Attributes порушує правила AAA для тестування. Зокрема, якщо ваш Arrange випадково викине виняток, перш ніж ви навіть дістанетесь до Закону, ваш тест проходить ... eek!
фрілен-м

2
Microsoft нарешті -то отримав раунд поновлення MSTest - v2 підтримує Assert.ThrowsException<T>і Assert.ThrowsExceptionAsync<T>- см blogs.msdn.microsoft.com/visualstudioalm/2017/02/25 / ...
Quango

62

якщо ви використовуєте 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


60

Якщо ви використовуєте MSTest, у якого спочатку не було ExpectedExceptionатрибуту, ви можете зробити це:

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

3
Це працює, але я взагалі не рекомендую цього, оскільки логіка надмірно складна. Не кажучи, що це суперечка, але врахуйте, якщо ви пишете цей блок коду для декількох тестів - 10s, 100s тестів. Цю логіку потрібно розробити на чітко розробленому методі утвердження. Дивіться інші відповіді.
steve

35

Будьте обережні при використанні очікуваного ексцепції, оскільки це може призвести до декількох підводних каменів, як показано тут:

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/


4
Зауважте, що NUnit 2.5 також підтримує синтаксис стилів Assert.Throws
nunit.com/index.php?p=releaseNotes&r=2.5

Те, як зупиняються одиничні тести, щоб повідомити вам про виняток при використанні ExpectedException, зводить мене з розуму. Чому MS вважає, що було б гарною ідеєю зробити ручний крок в автоматизованих тестах? Дякуємо за посилання.
Ant Ant

@Ant: MS скопіювала NUnit ... тому справжнє питання полягає в тому, чому NUnit думав, що це гарна ідея?
jrista

28

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

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

Ви можете встановити його з nuget: Install-Package MSTest.TestFramework


У 2018 році це вважається найкращою практикою, оскільки він перевіряє лише тестування одиниці, яка перевіряється, а не якийсь інший код.
CM CM

24

У проекті, над яким я працюю, у нас є ще одне рішення.

По-перше, мені не подобається, що очікуване 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;
    }

Акуратно, чи не так;)


14

Це атрибут методу тестування ... ви не використовуєте 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


Дякую за приклад. Чи є у вас приклад того, як протестувати Assert.DoesNotThrow () або еквівалент?
Lane Goolsby

10

Домогтися цього можна простою однолінійкою.

Якщо ваша операція foo.bar()асинхронізована:

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

Якщо foo.bar()це не асинхронізація

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

1
Є багато інших відповідей, для мене я шукав короткочасний спосіб перевірити на відомі умови відмови лише Типом виключення, це робить для найпростіших читабельних тестових випадків. ПРИМІТКА. Тип винятку не відповідає для успадкованих класів виключень, як-от стандартний пробний вхід, тому наведений вище приклад не захоплює ArgumentExceptionприклад. Старий спробу Catch та тестування відповіді на виключення все ще вдається, якщо у вас є розширені критерії для тестування, але для багатьох моїх випадків це дуже допомагає!
Кріс

5

Я не рекомендую використовувати атрибут 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, якщо потрібна додаткова перевірка. Але я не тільки повинен був би подумати про те, яку техніку використовувати для кожного тесту, але зміна коду з однієї методики на іншу в міру зміни потреб не була тривіальним зусиллям. Використання одного послідовного підходу економить розумові зусилля.

Отже, підсумовуючи, цей підхід займається спортом: простота у використанні, гнучкість та надійність (важко це зробити неправильно).


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
Хммм ... я розумію ідею, але не впевнений, що згоден, що краще. Тільки тому, що ми хочемо забезпечити підвищення конкретного винятку, не означає, що всі інші повинні бути завершені як невдача ствердження. IMHO невідомий виняток повинен просто перекинути стек, як це було б у будь-якій іншій операції затвердження.
Кроно

@Martin Я б видалив код, що включає винятокІнше, і просто відмовитися від другого пункту лову
Том Лінт

4

Оскільки ви згадуєте про використання інших тестових класів, кращим варіантом, ніж 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тестування асинхронних методів.


4

Як альтернативу, ви можете спробувати тестування, що винятки насправді викидаються з наступних 2 рядків у вашому тесті.

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

4

У тестуванні вбудованого модуля VS, якщо ви просто хочете перевірити, що "будь-який виняток" викинуто, але ви не знаєте типу, ви можете використовувати спіймання всіх:

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

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

Це залежатиме від того, яку структуру тесту ви використовуєте?

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

[ExpectedException(typeof(ArgumentException))]

2

У разі використання NUnit , спробуйте це:

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

2

Існує дивовижна бібліотека під назвою NFluent, яка прискорює та полегшує спосіб написання ваших тверджень .

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

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

1

Хоча це старе питання, я хотів би додати нову думку до дискусії. Я розширив шаблон очікування «Упорядкувати», «Акт», «Оцінити», «Упорядкувати», «Акт», «Затвердити». Ви можете зробити вказівник очікуваного винятку, а потім стверджувати, що йому призначено. Це відчуває себе чистішим, ніж робити ваші акти в блоці вилову, залишаючи розділ "Акт" здебільшого лише для одного рядка коду для виклику методу, який перевіряється. Вам також не потрібно 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.