C# 使用 Assert 测试异常以确保它们会被抛出的最佳方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/741029/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to test exceptions with Assert to ensure they will be thrown
提问by Hannoun Yassir
Do you think that this is a good way for testing exceptions? Any suggestions?
您认为这是测试异常的好方法吗?有什么建议?
Exception exception = null;
try{
//I m sure that an exeption will happen here
}
catch (Exception ex){
exception = ex;
}
Assert.IsNotNull(exception);
I'm using MS Test.
我正在使用 MS 测试。
采纳答案by tvanfosson
I have a couple of different patterns that I use. I use the ExpectedException
attribute most of the time when an exception is expected. This suffices for most cases, however, there are some cases when this is not sufficient. The exception may not be catchable - since it's thrown by a method that is invoked by reflection - or perhaps I just want to check that other conditions hold, say a transaction is rolled back or some value has still been set. In these cases I wrap it in a try/catch
block that expects the exact exception, does an Assert.Fail
if the code succeeds and also catches generic exceptions to make sure that a different exception is not thrown.
我使用了几种不同的模式。ExpectedException
大多数情况下,我会在预期出现异常时使用该属性。这对于大多数情况来说就足够了,但是,在某些情况下这还不够。异常可能无法捕获 - 因为它是由反射调用的方法抛出的 - 或者我可能只是想检查其他条件是否成立,比如事务已回滚或某些值仍被设置。在这些情况下,我将它包装在一个try/catch
需要确切异常的块中,Assert.Fail
如果代码成功则执行并捕获通用异常以确保不会抛出不同的异常。
First case:
第一种情况:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
var obj = new ClassRequiringNonNullParameter( null );
}
Second case:
第二种情况:
[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 )
);
}
}
回答by itowlson
Mark the test with the ExpectedExceptionAttribute (this is the term in NUnit or MSTest; users of other unit testing frameworks may need to translate).
使用 ExpectedExceptionAttribute 标记测试(这是 NUnit 或 MSTest 中的术语;其他单元测试框架的用户可能需要翻译)。
回答by Steve Willcock
With most .net unit testing frameworks you can put an [ExpectedException] attribute on the test method. However this can't tell you that the exception happened at the point you expected it to. That's where xunit.netcan help.
对于大多数 .net 单元测试框架,您可以在测试方法上放置 [ExpectedException] 属性。但是,这不能告诉您异常发生在您预期的时间点。这就是xunit.net可以提供帮助的地方。
With xunit you have Assert.Throws, so you can do things like this:
使用 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));
}
[Fact] is the xunit equivalent of [TestMethod]
[事实] 是 [TestMethod] 的 xunit 等价物
回答by GrahamS
As an alternative to using ExpectedException
attribute, I sometimes define two helpful methods for my test classes:
作为使用ExpectedException
属性的替代方法,我有时会为我的测试类定义两个有用的方法:
AssertThrowsException()
takes a delegate and asserts that it throws the expected exception with the expected message.
AssertThrowsException()
接受一个委托并断言它用预期的消息抛出预期的异常。
AssertDoesNotThrowException()
takes the same delegate and asserts that it does not throw an exception.
AssertDoesNotThrowException()
采用相同的委托并断言它不会引发异常。
This pairing can be very useful when you want to test that an exception is thrown in one case, but not the other.
当您想测试在一种情况下抛出异常而不是另一种情况时,这种配对非常有用。
Using them my unit test code might look like this:
使用它们,我的单元测试代码可能如下所示:
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);
Nice and neat huh?
漂亮整洁吧?
My AssertThrowsException()
and AssertDoesNotThrowException()
methods are defined on a common base class as follows:
MyAssertThrowsException()
和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);
}
}
回答by allgeek
I'm new here and don't have the reputation to comment or downvote, but wanted to point out a flaw in the example in Andy White's reply:
我是新来的,没有评论或投票的声誉,但想指出Andy White 回复中示例中的一个缺陷:
try
{
SomethingThatCausesAnException();
Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
// whatever logging code
}
In all unit testing frameworks I am familiar with, Assert.Fail
works by throwing an exception, so the generic catch will actually mask the failure of the test. If SomethingThatCausesAnException()
does not throw, the Assert.Fail
will, but that will never bubble out to the test runner to indicate failure.
在我熟悉的所有单元测试框架中,Assert.Fail
通过抛出异常来工作,因此通用捕获实际上会掩盖测试的失败。如果SomethingThatCausesAnException()
不抛出,Assert.Fail
则将,但永远不会冒泡到测试运行器以指示失败。
If you need to catch the expected exception (i.e., to assert certain details, like the message / properties on the exception), it's important to catch the specific expected type, and not the base Exception class. That would allow the Assert.Fail
exception to bubble out (assuming you aren't throwing the same type of exception that your unit testing framework does), but still allow validation on the exception that was thrown by your SomethingThatCausesAnException()
method.
如果您需要捕获预期的异常(即断言某些细节,例如异常的消息/属性),则捕获特定的预期类型很重要,而不是基本 Exception 类。这将允许Assert.Fail
异常冒泡(假设您没有抛出与单元测试框架相同类型的异常),但仍然允许对您的SomethingThatCausesAnException()
方法抛出的异常进行验证。
回答by StuartLC
As of v 2.5, NUnithas the following method-level Assert
s for testing exceptions:
从 v 2.5 开始,NUnit具有以下Assert
用于测试异常的方法级别:
Assert.Throws, which will test for an exact exception type:
Assert.Throws,它将测试一个确切的异常类型:
Assert.Throws<NullReferenceException>(() => someNullObject.ToString());
And Assert.Catch
, which will test for an exception of a given type, or an exception type derived from this type:
And Assert.Catch
,它将测试给定类型的异常或从该类型派生的异常类型:
Assert.Catch<Exception>(() => someNullObject.ToString());
As an aside, when debugging unit tests which throw exceptions, you may want to prevent VS from breaking on the exception.
顺便说一句,在调试抛出异常的单元测试时,您可能希望防止 VS中断异常。
Edit
编辑
Just to give an example of Matthew's comment below, the return of the generic Assert.Throws
and Assert.Catch
is the exception with the type of the exception, which you can then examine for further inspection:
为了给下面的,一般的回报马修的注释的例子 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);
回答by bytedev
Unfortunately MSTest STILL only really has the ExpectedException attribute (just shows how much MS cares about MSTest) which IMO is pretty awful because it breaks the Arrange/Act/Assert pattern and it doesnt allow you to specify exactly which line of code you expect the exception to occur on.
不幸的是,MSTest STILL 真的只有 ExpectedException 属性(只是显示了 MS 对 MSTest 的关心程度),IMO 非常糟糕,因为它打破了排列/行为/断言模式,并且它不允许您准确指定您期望异常的哪一行代码发生在。
When I'm using (/forced by a client) to use MSTest I always use this helper class:
当我使用 (/forced by a client) 来使用 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.");
}
}
Example of usage:
用法示例:
AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));
回答by Icaro Bombonato
Now, 2017, you can do it easier with the new MSTest V2 Framework:
现在,2017 年,您可以使用新的MSTest V2 框架更轻松地做到这一点:
Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());
//async version
await Assert.ThrowsExceptionAsync<SomeException>(
() => myObject.SomeMethodAsync()
);
回答by Shahar Shokrani
Suggest using NUnit's clean delegate syntax.
建议使用NUnit的干净委托语法。
Example for testing ArgumentNullExeption
:
测试示例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>);
}