NUnit:为什么不Assert.Throws <T> Catch My ArgumentNullException?

Mik*_*fer 15 .net c# nunit exception

我是在尊敬的John Skeet先生的要求下重新发布这个问题的,他建议我设计一个简单的测试程序,隔离并演示我遇到的问题并重新发布问题.这个问题多起来了这一个,所以请原谅我,如果这一切听起来很熟悉.您可以从中收集有关此问题的额外详细信息.

我遇到的问题Assert.Throws<T>来自NUnit 2.5.9.有时,它无法捕获TestDelegate调用的方法中抛出的任何异常.我已经在下面的代码中以可重现的方式确定了这种行为.(尽管如此,这可能是Fails On My Machine™的案例.

为了重现错误,我创建了一个包含两个C#DLL项目的解决方案:

  • 第一个包含一个类,只有一个公共方法.该方法是一种扩展方法,它封装了创建SqlCommand,填充其参数并ExecuteScalar在其上调用所需的逻辑.该项目不包括其他参考.
  • 第二个包含一个具有两个方法的类,用于测试第一个DLL中的方法是否按预期工作.该项目引用了第一个,并包含对NUnit Framework的引用.没有引用其他程序集.

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

  1. Assert.Throws正确调用ExecuteScalar<T>扩展方法.
  2. 正如预期的那样,参数值为null.
  3. ExecuteScalar<T> 测试其参数的空值.
  4. 调试器确实命中并执行包含的行throw new ArgumentNullException(...).
  5. 执行后throw,应用程序的控制权不会立即转移到Assert.Throws.相反,它继续在下一行ExecuteScalar<T>.
  6. 下一行代码执行后,调试器就会中断,并显示错误"用户代码未处理Argument null异常".

下面给出了隔离此行为的源代码.

扩展方法

namespace NUnit_Anomaly
{
    using System;
    using System.Data;
    using System.Data.SqlClient;

    public static class Class1
    {
        public static T ExecuteScalar<T>(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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

测试案例

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<ArgumentNullException>(() => ((SqlConnection)null).ExecuteScalar<int>(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<ArgumentNullException>(() => connection.ExecuteScalar<int>(null));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

实际效果是测试失败了.据我所知,Assert.Throws<T>应该抓住我的例外,测试应该通过.

UPDATE

我接受了Hans的建议并检查了Exceptions对话框.我没有打破抛出的异常,但我打破了未处理的用户异常.显然,这就是抛出异常时调试器进入IDE的原因.清除复选框修复了问题,并将其Assert.Throws<T>拾起.但是,如果我没有这样做,我不能只按F5继续执行,否则异常将成为一个NullReferenceException.

所以现在问题是:我可以基于每个项目配置异常中断吗?我只是想在测试时这样做,但不是一般的.

Rom*_*kov 15

而实际上,这Assert.Throws 吸引您的例外,但是Visual Studio中的停止第一次机会异常反正.您只需按F5即可查看; Visual Studio很乐意继续执行.

正如异常帮助器告诉您的那样,异常未被用户代码处理.因此我们知道Visual Studio因某些原因不认为NUnit是用户代码.

在此输入图像描述

如果您知道在哪里查看,Visual Studio实际上会以纯文本形式告诉您:

在此输入图像描述

堆栈跟踪中也有证据表明这一事实:

在此输入图像描述

解决方案1:使用NUnit的调试版本和调试符号.这将使Visual Studio将NUnit视为用户代码,从而停止将您的异常视为"未被用户代码处理".这不是微不足道的,但从长远来看可能会更好.

解决方案2:在Visual Studio的调试设置中关闭"启用我的代码"复选框:

在此输入图像描述

PS我不考虑解决方法,你Assert.Throws<T>完全避免使用,但当然有办法做到这一点.