Найкращий спосіб перевірити винятки за допомогою Assert, щоб переконатися, що їх буде видалено


97

Чи вважаєте ви, що це хороший спосіб перевірки винятків? Будь-які пропозиції?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);

Я використовую тест MS.

Відповіді:


137

У мене є кілька різних моделей, які я використовую. Я використовую ExpectedExceptionатрибут більшу частину часу, коли очікується виняток. Цього достатньо для більшості випадків, однак, бувають випадки, коли цього недостатньо. Виняток може бути неможливо вловити - оскільки він викликаний методом, який викликається відображенням, - або, можливо, я просто хочу перевірити, чи виконуються інші умови, скажімо, транзакція відкатана або якесь значення все ще встановлено. У цих випадках я обертаю його в try/catchблок, який очікує точного винятку, робить a, Assert.Failякщо код успішний, а також ловить загальні винятки, щоб переконатися, що інший виняток не викидається.

Перший випадок:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
     var obj = new ClassRequiringNonNullParameter( null );
}

Другий випадок:

[TestMethod]
public void MethodTest()
{
    try
    {
        var obj = new ClassRequiringNonNullParameter( null );
        Assert.Fail("An exception should have been thrown");
    }
    catch (ArgumentNullException ae)
    {
        Assert.AreEqual( "Parameter cannot be null or empty.", ae.Message );
    }
    catch (Exception e)
    {
        Assert.Fail(
             string.Format( "Unexpected exception of type {0} caught: {1}",
                            e.GetType(), e.Message )
        );
    }
}

16
Багато платформ модульного тестування реалізують помилки твердження як винятки. Тож Assert.Fail () у другому випадку потрапить під блок catch (Exception), який приховає повідомлення про виключення. Вам потрібно додати лову (NUnit.Framework.AssertionException) {throw;} або подібну - див. Мою відповідь.
GrahamS

@Graham - я набрав це на маківці. Зазвичай я також роздруковував повідомлення про виняток на додаток до його типу. Справа в тому, що тест не вдався б, оскільки другий обробник зафіксував помилку твердження та "здійснив повторний пошук" з інформацією про помилку.
tvanfosson

1
Хоча ваш код функціонально надійний, я не рекомендую використовувати атрибут ExpectedException (оскільки він занадто обмежує та схильний до помилок) або писати блок try / catch у кожному тесті (оскільки він занадто складний та схильний до помилок). Використовуйте добре розроблений метод ствердження - або наданий вашим тестовим фреймворком, або напишіть власний. Ви можете досягти кращого коду, і вам не доведеться вибирати між різними методами або переходити з одного на інший, коли тест змінюється. Дивіться stackoverflow.com/a/25084462/2166177
Стів

FYI - я перейшов до використання xUnit, який має строго набраний Assert.Throwsметод, який охоплює обидва ці випадки.
tvanfosson

Атрибут ExpectedException - це неприємний і застарілий спосіб перевірити, чи не виникають винятки. Дивіться мою повну відповідь нижче.
bytedev

43

Тепер, 2017, ви можете зробити це простіше за допомогою нової платформи MSTest V2 :

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);

Це вдасться лише в тому випадку, якщо System.Exceptionбуде кинуто. Будь-який інший, як-от, не System.ArgumentExceptionпройде тест.
sschoof

2
Якщо ви очікуєте іншого типу винятку, вам слід протестувати його ... У вашому прикладі ви повинні зробити: Assert.ThrowsException <ArgumentException> (() => myClass.MyMethodWithError ());
Ікаро Бомбонато

2
Щось важливе, на що слід звернути увагу, - це те, що використання Assert.ThrowsException<MyException>will перевіряє виключно на той самий тип винятків, який надається, а не на будь-який з його похідних типів винятків. У моєму прикладі, якщо тестоване Subбув Throwв MyInheritedException(похідний тип з базового класу MyException), то тест буде терпіти невдачі .
Ама,

Якщо ви хочете розширити свій тест і прийняти винятковий тип, а також похідні типи, використовуйте a Try { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}. Зверніть увагу на найважливіше значення Catch (AssertFailedException e) {throw;}(пор. Коментар від allgeek)
Ама,

17

Я тут новачок і не маю репутації коментувати або проти, але хотів вказати на недолік у прикладі у відповіді Енді Уайта :

try
{
    SomethingThatCausesAnException();
    Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
    // whatever logging code
}

У всіх знайомих мені фреймворках модульного тестування Assert.Failпрацює викид, тому загальний улов фактично замаскує провал тесту. Якщо SomethingThatCausesAnException()не кине, то Assert.Failзаповіт, але це ніколи не випливе до бігуна, що вказує на несправність.

Якщо вам потрібно вловити очікуваний виняток (тобто затвердити певні деталі, наприклад повідомлення / властивості винятку), важливо вловити конкретний очікуваний тип, а не базовий клас винятків. Це дозволило б Assert.Failвинятковій ситуації виділятися (припускаючи, що ви не створюєте той самий тип винятку, який робить ваш модуль модульного тестування), але все одно дозволить перевірку виключення, яке було видане вашим SomethingThatCausesAnException()методом.


15

Починаючи з версії 2.5, NUnit має такі рівні методу Asserts для тестування винятків:

Assert.Throws , який буде перевіряти точний тип виключення:

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());

І Assert.Catch, який перевірятиме виняток даного типу або тип виключення, похідний від цього типу:

Assert.Catch<Exception>(() => someNullObject.ToString());

Окрім того, під час налагодження модульних тестів, які видають винятки, вам може знадобитися не допустити, щоб VS зламав виняток .

Редагувати

Тільки для того, щоб навести приклад коментаря Метью нижче, повернення загального Assert.Throwsі Assert.Catchє винятком із типом винятку, який потім можна вивчити для подальшої перевірки:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);

2
Рой Ошерове рекомендує це у "Мистецтві юніт-тестування", друге видання, розділ 2.6.2.
Аві

2
Мені подобається Assert.Throws, крім того, він повертає виняток, щоб ви могли писати подальші твердження щодо самого винятку.
Matthew

Питання було для MSTest, а не NUnit.
bytedev

Оригінальне запитання @nashwan OP не мало такої кваліфікації, і тегування досі не відповідає вимогам MS-Test. У такому вигляді це питання C #, .Net, Unit-Testing.
StuartLC

11

На жаль, у MSTest ЩЕ ВСЕ справді є лише атрибут ExpectedException (просто показує, наскільки MS дбає про MSTest), який IMO є досить жахливим, оскільки порушує шаблон Arrange / Act / Assert, і він не дозволяє точно вказати, в якому рядку коду ви очікуєте виняток відбуватися на.

Коли я використовую (/ змушений клієнтом) використовувати MSTest, я завжди використовую цей допоміжний клас:

public static class AssertException
{
    public static void Throws<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }

    public static void Throws<TException>(Action action, string expectedMessage) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            Assert.AreEqual(expectedMessage, ex.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + ex.Message + "' was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }
}

Приклад використання:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));

10

Як альтернативу використанню ExpectedExceptionатрибута, я іноді визначаю два корисні методи для своїх тестових класів:

AssertThrowsException() приймає делегата і стверджує, що він видає очікуваний виняток із очікуваним повідомленням.

AssertDoesNotThrowException() приймає того самого делегата і стверджує, що він не створює виключення.

Це сполучення може бути дуже корисним, коли ви хочете перевірити, що в одному випадку виникає виняток, а в іншому - не.

З їх використанням мій модульний тестовий код може виглядати так:

ExceptionThrower callStartOp = delegate(){ testObj.StartOperation(); };

// Check exception is thrown correctly...
AssertThrowsException(callStartOp, typeof(InvalidOperationException), "StartOperation() called when not ready.");

testObj.Ready = true;

// Check exception is now not thrown...
AssertDoesNotThrowException(callStartOp);

Приємно і акуратно, так?

My AssertThrowsException()та AssertDoesNotThrowException()методи визначаються на загальному базовому класі наступним чином:

protected delegate void ExceptionThrower();

/// <summary>
/// Asserts that calling a method results in an exception of the stated type with the stated message.
/// </summary>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
/// <param name="expectedExceptionType">The expected type of the exception, e.g. typeof(FormatException).</param>
/// <param name="expectedExceptionMessage">The expected exception message (or fragment of the whole message)</param>
protected void AssertThrowsException(ExceptionThrower exceptionThrowingFunc, Type expectedExceptionType, string expectedExceptionMessage)
{
    try
    {
        exceptionThrowingFunc();
        Assert.Fail("Call did not raise any exception, but one was expected.");
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(expectedExceptionType, ex, "Exception raised was not the expected type.");
        Assert.IsTrue(ex.Message.Contains(expectedExceptionMessage), "Exception raised did not contain expected message. Expected=\"" + expectedExceptionMessage + "\", got \"" + ex.Message + "\"");
    }
}

/// <summary>
/// Asserts that calling a method does not throw an exception.
/// </summary>
/// <remarks>
/// This is typically only used in conjunction with <see cref="AssertThrowsException"/>. (e.g. once you have tested that an ExceptionThrower
/// method throws an exception then your test may fix the cause of the exception and then call this to make sure it is now fixed).
/// </remarks>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
protected void AssertDoesNotThrowException(ExceptionThrower exceptionThrowingFunc)
{
    try
    {
        exceptionThrowingFunc();
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow any NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.Fail("Call raised an unexpected exception: " + ex.Message);
    }
}

4

Позначте тест за допомогою ExpectedExceptionAttribute (це термін у NUnit або MSTest; користувачам інших модульних модулів тестування може знадобитися перекласти).


Не використовуйте ExpectedExceptionAttribute (причина вказана в моєму дописі нижче). NUnit має Assert.Throws <YourException> (), а для MSTest використовуйте щось на зразок мого класу AssertException нижче.
bytedev

3

У більшості платформ модульного тестування .net ви можете додати атрибут [ExpectedException] до методу тестування. Однак це не може сказати вам, що виняток стався в той момент, коли ви цього очікували. Ось тут xunit.net може допомогти.

З xunit у вас є Assert.Throws, щоб ви могли робити такі речі:

    [Fact]
    public void CantDecrementBasketLineQuantityBelowZero()
    {
        var o = new Basket();
        var p = new Product {Id = 1, NetPrice = 23.45m};
        o.AddProduct(p, 1);
        Assert.Throws<BusinessException>(() => o.SetProductQuantity(p, -3));
    }

[Факт] є еквівалентом xunit [TestMethod]


Якщо вам потрібно використовувати MSTest (до чого мене часто змушують роботодавці), тоді дивіться мою відповідь нижче.
bytedev

0

Запропонуйте використовувати чистий синтаксис делегатів NUnit .

Приклад для тестування ArgumentNullExeption:

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.