如何使用断言来验证异常被抛出?
-
06-09-2019 - |
题
我如何使用断言(或其他测试类?)来验证异常被抛出?
解决方案
有关“Visual Studio团队测试”看来你应用的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();
}
我的用于实现该优选的方法是写一个称为抛出方法,以及使用它就像任何其他Assert方法。不幸的是,.NET不允许你写一个静态扩展方法,这样你就不能使用这种方法,就好像它实际上属于在断言类的构建;只是让另一个叫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());
}
它看起来和行为更像是单元测试语法的其余部分。
如果您正在使用MSTest的,原本没有一个ExpectedException
属性,你可以这样做:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
如果您使用NUnit,你可以做这样的事情:
Assert.Throws<ExpectedException>(() => methodToTest());
,点击 另外,也可以以被抛出的异常存储为了进一步验证它:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
警惕使用的ExpectedException的,因为它可以导致几种缺陷如这里证明:
HTTP:// geekswithblogs。净/ sdorman /存档/ 2009/01/17 /单元测试 - 和 - 预期-exceptions.aspx
和这里:
http://xunit.github.io/docs/comparisons.html
如果您需要测试的例外,也有在方式少皱眉。您可以使用try {ACT /失败} {赶上断言}方法,它可以为不具备比其他的ExpectedException测试异常直接支持框架是有用的。
一个更好的选择是使用xUnit.NET,这是一个非常现代化的,前瞻性的,可扩展的单元测试框架已经与所有其他错误中汲取教训,并改进。一个这样的改进是Assert.Throws,它提供了断言例外一个更好的语法。
您可以在github上找到xUnit.NET: http://xunit.github.io/
在项目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;
}
纯,isn't它;)
MSTest的(V2)现在具有可以像这样使用的Assert.ThrowsException功能:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
您可以用的NuGet安装:Install-Package MSTest.TestFramework
这是在测试方法的属性......你不使用断言。看起来像这样:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
您可以下载使用从的NuGet包: PM>安装,包装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 /博客/ 2012/01 /断言的例外功能于MSTEST-with.html
可以用一个简单的一行实现这一点。
如果您的操作foo.bar()
是异步:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
如果foo.bar()
不是异步
Assert.ThrowsException<Exception>(() => foo.bar());
我不推荐使用的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)
}
注意
返回的异常,而不是支持一个验证回调合理的办法,除非这样做使得这个断言的调用语法非常不同的比其他断言我用。
不像其他人,我使用“传播”而不是“抛出”,因为我们只能测试异常是否从呼叫传播。我们不能直接测试抛出一个异常。但是,我想你可以像抛出的意思:抛出没有捕获
最后的思考
在切换到这种方法,我使用的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 ”小号 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
。
作为替代你可以尝试测试异常实际上与接下来的2线在您的测试被抛出。
var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);
嗯,我会非常总结别人都在这里以前说过......不管怎么说,这是我根据好答案内置的代码:)所有剩下要做的就是复制和使用...
/// <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.");
}
在VS内置的单元测试,如果你只是想验证“任何异常”被抛出,但你不知道的类型,你可以使用一个包罗万象的:
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
//...
}
查核 NUnit的文档约例子:
[ExpectedException( typeof( ArgumentException ) )]
这是要取决于你使用的是什么测试框架呢?
在MbUnit的,例如,你可以用一个属性指定的预期异常,以确保你得到你真正所期望的除外。
[ExpectedException(typeof(ArgumentException))]
在使用的情况下的 NUnit的下,尝试这样:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
尽管这是一个老问题,我想新的思路添加到讨论。我已经扩展了安排,法,断言可以预期的模式,安排,法,断言。您可以预期的异常指针,然后断言它被分配到。这种感觉不是做你的断言在catch块清洁,主要是让你的法案部分只是为了一行代码来调用测试方法。你也不必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);
}
有被称为真棒库 NFluent 其中的加快并简化你的方式写你的断言的。
有非常简单写断言用于抛出异常:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}