Best way to test exceptions with Assert to ensure they will be thrown
您认为这是测试异常的好方法吗? 有什么建议?
1 2 3 4 5 6 7 8 9 | Exception exception = null; try{ //I m sure that an exeption will happen here } catch (Exception ex){ exception = ex; } Assert.IsNotNull(exception); |
我正在使用MS Test。
我使用了几种不同的模式。当预期出现异常时,我大部分时间都使用
第一种情况:
1 2 3 4 5 6 |
第二种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | [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 ) ); } } |
现在,2017年,您可以使用新的MSTest V2 Framework轻松完成:
1 | Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError()); |
我是新来的,并没有评论或downvote的声誉,但想在安迪怀特的回复中指出这个例子中的一个缺陷:
1 2 3 4 5 6 7 8 9 | try { SomethingThatCausesAnException(); Assert.Fail("Should have exceptioned above!"); } catch (Exception ex) { // whatever logging code } |
在我熟悉的所有单元测试框架中,
如果需要捕获预期的异常(即,断言某些细节,例如异常上的消息/属性),捕获特定的预期类型而不是基本的Exception类很重要。这将允许
从v 2.5开始,NUnit具有以下用于测试异常的方法级
Assert.Throws,它将测试一个确切的异常类型:
1 | Assert.Throws<NullReferenceException>(() => someNullObject.ToString()); |
1 | Assert.Catch<Exception>(() => someNullObject.ToString()); |
另外,在调试抛出异常的单元测试时,您可能希望阻止VS在异常上中断。
编辑
仅举一个下面马修评论的例子,泛型
1 2 3 | // The type of ex is that of the generic type parameter (SqlException) var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks()); Assert.AreEqual(1205, ex.Number); |
不幸的是,MSTest STILL只有ExpectedException属性(只显示MS对MSTest的关注程度)IMO非常糟糕,因为它打破了Arrange / Act / Assert模式,并且它不允许您准确指定您期望异常的代码行发生在。
当我使用(/被客户端强制)使用MSTest时,我总是使用这个助手类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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."); } } |
用法示例:
1 | AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null)); |
作为使用
当您想要测试在一种情况下抛出异常而不是另一种情况时抛出异常时,此配对非常有用。
使用它们我的单元测试代码可能如下所示:
1 2 3 4 5 6 7 8 9 | 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); |
好又整洁吧?
我的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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); } } |
使用ExpectedExceptionAttribute标记测试(这是NUnit或MSTest中的术语;其他单元测试框架的用户可能需要翻译)。
对于大多数.net单元测试框架,您可以在测试方法上放置[ExpectedException]属性。但是,这不能告诉您异常发生在您期望的那一刻。这就是xunit.net可以提供帮助的地方。
使用xunit,你有Assert.Throws,所以你可以做这样的事情:
1 2 3 4 5 6 7 8 |
[事实]是[TestMethod]的xunit等价物
建议使用NUnit的干净委托语法。
测试示例
1 2 3 4 5 6 7 8 9 10 |