NUnit:为什么不Assert.Throws Catch My ArgumentNullException?
我是在尊敬的John Skeet先生的要求下重新发布这个问题的,他建议我设计一个简单的测试程序,隔离并演示我遇到的问题并重新发布问题。 这个问题源于这个问题,如果听起来很熟悉,请原谅我。 您可以从中收集有关此问题的额外详细信息。
我遇到的问题是关于来自NUnit 2.5.9的Assert.Throws
。 有时,它将无法捕获TestDelegate调用的方法中抛出的任何exception。 我已经在下面的代码中以可重现的方式确定了这种行为。 (诚然,这可能是Fails On My Machine™的情况)。
为了重现错误,我创建了一个包含两个C#DLL项目的解决方案:
- 第一个包含一个类,只有一个公共方法。 该方法是一种扩展方法,它封装了创建
SqlCommand
所需的逻辑,填充其参数并在其上调用ExecuteScalar
。 该项目不包括其他参考。 - 第二个包含一个带有两个方法的类,用于测试第一个DLL中的方法是否按预期工作。 该项目引用了第一个,并包含对NUnit Framework的引用。 没有引用其他程序集。
当我在调试器中单步执行测试时,我会观察到以下内容:
-
Assert.Throws
正确调用ExecuteScalar
扩展方法。 - 正如预期的那样,参数值为null。
-
ExecuteScalar
测试其参数的空值。 - 调试器确实命中并执行包含
throw new ArgumentNullException(...)
。 - 执行
throw
,应用程序的控制不会立即转移到Assert.Throws
。 相反,它继续在ExecuteScalar
的下一行。 - 下一行代码执行后,调试器就会中断,并显示错误“参数nullexception未被用户代码处理”。
下面给出了隔离此行为的源代码。
扩展方法
namespace NUnit_Anomaly { using System; using System.Data; using System.Data.SqlClient; public static class Class1 { public static T ExecuteScalar(this SqlConnection connection, string sql) { if (connection == null) { throw new ArgumentNullException("connection"); } if (sql == null) { throw new ArgumentNullException("sql"); } using (var command = connection.CreateCommand()) { command.CommandType = CommandType.Text; command.CommandText = sql; return (T)command.ExecuteScalar(); } } } }
测试案例
namespace NUnit_Tests { using System; using System.Data.SqlClient; using System.Diagnostics; using NUnit.Framework; using NUnit_Anomaly; [TestFixture] public class NUnitAnomalyTest { [Test] public void ExecuteDataSetThrowsForNullConnection() { Assert.Throws(() => ((SqlConnection)null).ExecuteScalar(null)); } [Test] public void ExecuteDataSetThrowsForNullSql() { const string server = "MY-LOCAL-SQL-SERVER"; const string instance = "staging"; string connectionString = String.Format("Data Source={0};Initial Catalog={1};Integrated Security=True;", server, instance); using (var connection = new SqlConnection(connectionString)) { Assert.Throws(() => connection.ExecuteScalar(null)); } } } }
实际效果是测试失败了。 据我所知, Assert.Throws
应该捕获我的exception并且测试应该通过。
UPDATE
我接受了Hans的建议并检查了Exceptions对话框。 我没有打破抛出的exception,但我打破了未处理的用户exception。 显然,这就是抛出exception时调试器进入IDE的原因。 清除复选框修复了问题, Assert.Throws
将其拾起。 但是,如果我没有这样做,我不能只按F5继续执行,否则exception将成为NullReferenceException
。
所以现在问题是:我可以基于每个项目配置exception中断吗? 我只是想在测试时这样做,但不是一般的。
实际发生的是Assert.Throws
会捕获您的exception,但无论如何Visual Studio都会在第一次机会exception时停止。 您只需按F5即可查看; Visual Studio很乐意继续执行。
正如exception帮助器告诉您的那样,exception未被用户代码处理 。 所以我们知道Visual Studio不会因为某些原因将NUnit视为用户代码。
如果您知道在哪里查看,Visual Studio实际上会以纯文本forms告诉您:
堆栈跟踪中还有证据表明这一事实:
解决方案1 :使用NUnit的调试版本和调试符号。 这将使Visual Studio将NUnit视为用户代码 ,从而停止将您的exception视为“未被用户代码处理”。 这不是微不足道的,但从长远来看可能会更好。
解决方案2 :在Visual Studio的调试设置中关闭“启用我的代码”复选框:
PS我不考虑解决方法,你可以避免使用Assert.Throws
,但当然有办法做到这一点。