2009-06-01 6 views
627

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

+0

Какую модульную систему тестирования вы используете? –

+3

Visual Studio Integrated – Alex

+3

Не используется атрибут ExpectedException? ref: http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.expectedexceptionattribute.aspx – shahkalpesh

ответ

759

Для "Visual Team Team Test", похоже, вы применяете атрибут ExpectedException к методу теста.

Пример из документации здесь: A Unit Testing Walkthrough with 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]"); 
} 
+22

Атрибут ExpectedException выше работает и в NUnit (но [TestMethod] должен быть [Test]). – dbkk

+3

@dbkk: Не работает точно так же в NUnit - сообщение обрабатывается как строка, которая должна отвечать на сообщение об ошибке (и я думаю, что это имеет смысл) –

+22

Этот атрибут выполняет свою работу и является встроенной функцией для C#, но я не рекомендую использовать его, поскольку он недостаточно гибкий. Подумайте, что произойдет, если тип исключения будет указан вашим тестовым установочным кодом: test pass, но на самом деле не сделал то, что вы ожидали. Или что делать, если вы хотите проверить состояние объекта исключения. Обычно я хочу использовать StringAssert.Contains (e.Message ...), а не тестировать все сообщение. Используйте метод assert, как описано в других ответах. – steve

213

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

try { 
    somethingThatShouldThrowAnException(); 
    Assert.Fail(); // If it gets to this line, no exception was thrown 
} catch (GoodException) { } 

Как @Jonas указывает, это не работает для ловли базовый Exception:

try { 
    somethingThatShouldThrowAnException(); 
    Assert.Fail(); // raises AssertionException 
} catch (Exception) { 
    // Catches the assertion exception, and the test passes 
} 

Если вы абсолютно необходимо поймать исключение, вам нужно повторно сбросить Assert.Fail(). Но на самом деле, это знак, который вы не должны писать вручную; проверьте свою тестовую структуру для параметров или посмотрите, можете ли вы использовать более значимое исключение для тестирования.

Вы должны иметь возможность адаптировать этот подход к любым вашим потребностям - включая указание каких исключений для улова. Если вы только ожидать определенных типов, закончить catch блоки прочь с:

} catch (GoodException) { 
} catch (Exception) { 
    // not the right kind of exception 
    Assert.Fail(); 
} 
+17

+1, я использую этот путь вместо этого атрибута, когда мне нужно сделать утверждения, выходящие за пределы типа исключения. Например, что, если нужно проверить, что определенные поля в экземпляре исключения установлены на определенные значения. –

+0

Мне это нравится, потому что вам не нужно указывать точное сообщение об ошибке, например, с помощью подхода атрибута – user12345613

+2

У вас нет необходимости указывать сообщение об ошибке. Этого достаточно: [ExpectedException (typeof (ArgumentException))] – mibollma

14

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

[ExpectedException(typeof(ExceptionType))] 
public void YourMethod_should_throw_exception() 
3

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

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

[ExpectedException(typeof(ArgumentException))] 
2

Заканчивать nUnit Docs примеры о:

[ExpectedException(typeof(ArgumentException))] 
50

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

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

Это действительно работает, но я не рекомендую это вообще, так как логика слишком сложна. Не сказать, что это запутанно, но подумайте, напишите ли вы этот блок кода для нескольких тестов - 10 с, 100 с тестов. Эта логика должна быть обработана до хорошо продуманного метода assert. См. Другие ответы. – steve

36

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

+4

Обратите внимание, что теперь NUnit 2.5 также поддерживает синтаксис синтаксиса Assert.Throws - http://nunit.com/index.php?p=releaseNotes&r=2.5 – Alconja

+0

То, как завершаются тесты устройства, чтобы вы знали об исключении при использовании ExpectedException сводит меня с ума. Почему MS подумала, что было бы неплохо иметь ручной шаг в автоматизированных тестах? Спасибо за ссылки. – Ant

+0

@Ant: MS скопировал NUnit ... так что реальный вопрос: почему NUnit подумал, что это хорошая идея? – jrista

23

В виде i'm проекта работает на нас есть другое решение, делая это.

Сначала мне не нравится ExpectedExceptionAttribute, потому что он принимает во внимание, какой вызов метода вызвал Исключение.

Я делаю это вместо 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; 
    } 

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

92

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

который выглядит и ведет себя гораздо больше похож на остальных модульных тестов синтаксисов.

+0

, так что вам не нравятся атрибуты? –

+1

Избавьтесь от флага bool и поместите бросок в строку непосредственно после вызова для более компактной реализации. –

+11

Единственное, что делает это лучше, - это вернуть функцию исключенному исключению, чтобы вы могли продолжать утверждать, что такие вещи, как атрибуты исключения, верны. –

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."); 
} 
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 неизвестное исключение должно просто пузырить стек, как это было бы в любой другой операции подтверждения. – Crono

+0

@Martin Я удаляю код с участием exceptionOther и просто удаляю из второго предложения catch –

27

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

+0

Это также работает с MSTest. –

5

Я не рекомендую использовать атрибут 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) 
    } 

ПРИМЕЧАНИЯ

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

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

FINAL МЫСЛЬ

Прежде чем перейти к такому роду подход я считал с помощью атрибута ExpectedException, когда тест только проверить тип исключения и использование попытки/поймать блок, если требуется более проверка. Но не только я должен был подумать о том, какой метод использовать для каждого теста, но изменение кода от одного метода к другому по мере изменения потребностей было не тривиальным усилием. Использование одного последовательного подхода позволяет сэкономить умственные усилия.

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

13

Вы можете скачать пакет с NuGet помощью: PM> Install-Package MSTestExtensions, который добавляет Assert.Throws() синтаксис в стиле NUnit/XUnit к MSTest.

инструкции высокого уровня: скачать сборку и наследовать от BaseTest и вы можете использовать Assert.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

+0

Спасибо за пример. Есть ли у вас пример тестирования Assert.DoesNotThrow() или эквивалента? –

1

Несмотря на то, что это старый вопрос, я хотел бы добавить новую мысль к обсуждению. Я продлил шаблон Arrange, Act, Assert, который должен быть ожидаемым, упорядочить, действовать, утверждать. Вы можете сделать ожидаемый указатель исключения, а затем утверждать, что ему присвоен. Это кажется более чистым, чем выполнение ваших утверждений в блоке 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); 
} 
3

Поскольку вы упоминаете с помощью других тестовых классов, лучший вариант, чем атрибут 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.

5

MSTest теперь имеет функцию Assert.ThrowsException, которая может быть использована, как это:

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

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

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

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

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

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

[TestMethod] 
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)] 
public void ThrowExceptionTest() 
{ 
    //... 
} 
Смежные вопросы