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的引用。 没有引用其他程序集。

当我在调试器中单步执行测试时,我会观察到以下内容:

  1. Assert.Throws正确调用ExecuteScalar扩展方法。
  2. 正如预期的那样,参数值为null。
  3. ExecuteScalar测试其参数的空值。
  4. 调试器确实命中并执行包含throw new ArgumentNullException(...)
  5. 执行throw ,应用程序的控制不会立即转移到Assert.Throws 。 相反,它继续在ExecuteScalar的下一行。
  6. 下一行代码执行后,调试器就会中断,并显示错误“参数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 ,但当然有办法做到这一点。