如何使用断言来验证是否已使用MSTest抛出异常?

如何使用Assert(或其他测试类)来验证在使用MSTest/Microsoft. VisualStudio. TestTools. UnitTest时是否引发了异常?

717393 次浏览

通常你的测试框架会对此有答案。但是如果它不够灵活,你也可以这样做:

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,你需要重新抛出Asser. Fail()。但实际上,这表明你不应该手写这个;检查你的测试框架中的选项,或者看看你是否可以抛出更有意义的异常来测试。

catch (AssertionException) { throw; }

你应该能够将这种方法调整到你喜欢的任何地方——包括指定要捕获哪些类型的异常。如果你只期望某些类型,请用以下方式完成catch块:

} catch (GoodException) {} catch (Exception) {// not the right kind of exceptionAssert.Fail();}

它是测试方法上的一个属性……你不使用断言。看起来像这样:

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

在“Visual Studio Team Test”中,您似乎对测试的方法应用了EXECTEDException属性。

样本从这里留档:使用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");}

这将取决于您使用的是什么测试框架?

例如,在MbUnit中,您可以使用属性指定预期的异常,以确保您获得真正期望的异常。

[ExpectedException(typeof(ArgumentException))]

查看nUnit文档有关以下示例:

[ExpectedException( typeof( ArgumentException ) )]

如果您使用的是MSTest,它最初没有ExpectedException属性,您可以这样做:

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

要小心使用的期望异常,因为它可能会导致几个陷阱,如这里所示:

链接

在这里:

http://xunit.github.io/docs/comparisons.html

如果您需要测试异常,则不太反对方法。您可以使用try{act/fail}catch{assert}方法,这对于不直接支持ExpectedException以外的异常测试的框架很有用。

更好的选择是使用xUnit.NET,这是一个非常现代、前瞻性和可扩展的单元测试框架,它从所有其他错误中吸取了教训并进行了改进。其中一个改进是Assert.Throws,它为断言异常提供了更好的语法。

您可以在github上找到xUnit.NET:http://xunit.github.io/

在我正在做的一个项目中,我们有另一个解决方案。

首先,我不喜欢期望异常属性,因为它确实考虑了导致异常的方法调用。

我用一个辅助方法来代替。

测试

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

辅助方法

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

漂亮,不是吗;)

我更喜欢的实现方法是编写一个名为Throws的方法,并像使用其他As的方法一样使用它。不幸的是,. NET不允许你编写静态扩展方法,所以你不能使用这个方法,就好像它实际上属于构建在Aste类中一样;只需创建另一个名为MyAste或类似的东西。类看起来像这样:

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

它的外观和行为更像单元测试语法的其余部分。

好吧,我会总结一下这里其他人之前说过的话……不管怎样,这是我根据好答案构建的代码:)剩下要做的就是复制和使用……

/// <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.");}

上面@Richiban提供的helper很棒,只是它不处理抛出异常的情况,但不是预期的类型。以下解决了:

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

如果你使用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

我不建议在每个测试中使用EXECTEDException属性(因为它太受约束且容易出错)或编写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)}

返回异常而不是支持验证回调是一个合理的想法,但这样做会使此断言的调用语法与我使用的其他断言非常不同。

与其他人不同,我使用“传播”而不是“抛出”,因为我们只能测试异常是否从调用中传播。我们不能直接测试异常是否被抛出。但我想你可以想象抛出的意思是:抛出而不是捕获。

最后的想法

在切换到这种方法之前,我考虑过在测试仅验证异常类型时使用EXECTEDException属性,如果需要更多验证,则使用try/cat块。但是,我不仅必须考虑为每个测试使用哪种技术,而且随着需求的变化将代码从一种技术更改为另一种技术并非易事。使用一种一致的方法可以节省脑力劳动。

总之,这种方法具有:易用性、灵活性和稳健性(很难出错)。

您可以使用:PM>安装包从Nuget下载一个包,该包将断言抛出 nUnit/xUnit样式的语法添加到MsTest。

高级说明:下载程序集并从基础测试继承,您可以使用断言抛出语法。

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

尽管这是一个老问题,但我想在讨论中添加一个新的想法。我扩展了预期的排列、行为、断言模式,排列、行为、断言。你可以制作一个预期的异常指针,然后断言它被分配给。这感觉比在cat块中做断言更干净,留下你的行为部分主要只是为了一行代码来调用测试中的方法。你也不必从代码中的多个点Assert.Fail();return。抛出的任何其他异常都将导致测试失败,因为它不会被捕获,如果抛出了您期望类型的异常,但它不是您期望的异常,针对异常的消息或其他属性断言有助于确保您的测试不会无意中通过。

[TestMethod]public void Bar_InvalidDependency_ThrowsInvalidOperationException(){// ExpectationsInvalidOperationException expectedException = null;string expectedExceptionMessage = "Bar did something invalid.";
// ArrangeIDependency dependency = DependencyMocks.Create();Foo foo = new Foo(dependency);
// Acttry{foo.Bar();}catch (InvalidOperationException ex){expectedException = ex;}
// AssertAssert.IsNotNull(expectedException);Assert.AreEqual(expectedExceptionMessage, expectedException.Message);}

由于您提到使用其他测试类,因此比ExpectedException属性更好的选择是使用大声地应该扔

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用于异步方法测试。

MSTest(v2)现在有一个Asser. ThrowsException函数,可以像这样使用:

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

您可以使用nuget安装它:Install-Package MSTest.TestFramework

作为替代方案,您可以尝试测试异常实际上是与测试中的下两行一起抛出的。

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

如果使用NUnit,请尝试以下操作:

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

在VS内置的单元测试中,如果你只是想验证是否抛出了“任何异常”,但你不知道类型,你可以使用catall:

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

您可以通过简单的单行实现这一点。

如果您的操作foo.bar()是异步的:

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

如果foo.bar()不是异步

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

有一个很棒的库,叫做NFluent 加快和简化你写断言的方式

编写抛出异常的断言非常简单:

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

我知道这个线程很老,有很多很好的答案,但也许值得一提的是,本地函数可以以一种非常简单的方式提供帮助。

//Arrange
//Actvoid LocalFunction() => mr.ActualMethod(params);
//AssertAssert.Throws<Exception>(LocalFunction);

这适用于Visual Studio团队测试(a. k. aMSTest
在处理数据库或超文本传输协议事务时。系统应该在某个地方抛出异常,使用断言。抛出异常A sync<>()将捕获您的Throw事件。(在这些情况下,断言。抛出异常<>()不会捕获异常)。

   [TestMethod]public void Invalid_Input_UserName_Should_Throw_Exception(){await Assert.ThrowExceptionAsync<ExpectedExceptionType>(()=> new LogonInfo(InvalidInputInUserNameFormat,"P@ssword"));}

流利断言相关示例

为使用该库的用户添加一个使用FluentAssertions的示例。

// actAction result = () => {sut.DoSomething();};
// assertresult.Should().Throw<Exception>();

异步示例

// actFunc<Task> result = async () => {await sut.DoSomethingAsync();};
// assertawait result.Should().ThrowAsync<Exception>();