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

时间:2011-01-24 17:58:59

标签: c# .net exception nunit

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

我遇到的问题是来自NUnit 2.5.9的Assert.Throws<T>。有时,它无法捕获由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. 的行
  6. 执行throw后,对应用程序的控制不会立即转移到Assert.Throws。相反,它继续在ExecuteScalar<T>
  7. 的下一行
  8. 下一行代码执行后,调试器就会中断,并显示错误“用户代码未处理Argument null异常。”
  9. 下面给出了隔离此行为的源代码。

    扩展方法

    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();
                }
            }
        }
    }
    

    测试案例

    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));
                }
            }
        }
    }
    

    净效果是测试失败了。据我所知,Assert.Throws<T>应该捕获我的异常并且测试应该通过。

    更新

    我接受了汉斯的建议并检查了“例外”对话框。我没有打破抛出的异常,但我打破了未处理的用户异常。显然,这就是抛出异常时调试器进入IDE的原因。清除复选框可修复问题,Assert.Throws<T>将其选中。但是,如果我没有这样做,我不能只按F5继续执行,或者异常将成为NullReferenceException

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

1 个答案:

答案 0 :(得分:15)

实际发生的是,Assert.Throws 捕获您的异常,但无论如何,Visual Studio会停止第一次机会异常。您只需按F5即可查看; Visual Studio很乐意继续执行。

正如异常帮助程序告诉您的那样,异常未由用户代码处理。因此我们知道Visual Studio因某种原因不认为NUnit是用户代码。

enter image description here

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

enter image description here

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

enter image description here

解决方案1 ​​:使用NUnit的调试版本和调试符号。那个will get Visual Studio to regard NUnit as user code,因此停止将您的异常视为“未被用户代码处理”。这不是微不足道的,但从长远来看可能会更好。

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

enter image description here

P.S。我不考虑解决方法,你完全避免使用Assert.Throws<T>,但当然有办法实现这一点。