NUnit:为什么不 Assert.Throws捕获我的 ArgumentNullException?

发布于 2024-10-13 08:58:53 字数 3453 浏览 1 评论 0原文

我应尊敬的约翰·斯基特先生的要求重新发布这个问题,他建议我设计一个简单的测试程序来隔离和演示我遇到的问题并重新发布该问题。这个问题源于这个,所以请如果这一切听起来很熟悉,请原谅我。您可以从该问题中收集有关此问题的更多详细信息。

我遇到的问题涉及 NUnit 2.5.9 中的 Assert.Throws。有时,它无法捕获 TestDelegate 调用的方法中抛出的任何异常。我在下面的代码中以可重现的方式确定了这种行为。 (诚​​然,这可能是 Fails On My Machine™ 的情况。

为了重现该错误,我使用两个 C# DLL 项目创建了一个解决方案:

  • 第一个包含一个类,具有一个公共方法。该方法是扩展方法封装了创建 SqlCommand 所需的逻辑,填充其参数并对其调用 ExecuteScalar 该项目不包含其他引用。
  • 第二个包含一个具有两个类的类 。测试第一个 DLL 中的方法是否按预期工作的方法。该项目引用了第一个,并且包含对 NUnit 框架的引用。

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

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

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

扩展方法

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 应该捕获我的异常并且测试应该通过。

更新

我采纳了汉斯的建议并检查了“例外”对话框。我没有在抛出异常上中断,但在未处理的用户异常上中断。显然,这就是调试器在抛出异常时中断到 IDE 的原因。清除复选框解决了问题,Assert.Throws 解决了这个问题。但是,如果我没有这样做,我就不能只按F5继续执行,否则异常将变成NullReferenceException

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

I am posting this question anew at the behest of the distinguished Mr. John Skeet, who suggested I devise a simple test program that isolates and demonstrates the issue I am encountering and repost the question. This question grew out of this one, so please forgive me if it all sounds very familiar. You can potentially glean extra details about this question from that one.

The issue I am encountering regards Assert.Throws<T> from NUnit 2.5.9. It will, on occasion, fail to catch any exceptions thrown in the method invoked by the TestDelegate. I have pinned down this behavior in a reproducible manner in the code below. (Though this may, admittedly, be a case of Fails On My Machine™.

To reproduce the error, I've created a solution with two C# DLL projects:

  • The first contains one class, with a single public method. That method is an extension method that encapsulates the logic required to create a SqlCommand, populate its parameters and invoke ExecuteScalar on it. This project includes no other references.
  • The second contains a single class with two methods that test whether or not the method in the first DLL is working as expected. This project references the first, and includes a reference to the NUnit Framework. No other assemblies are referenced.

When I step through the tests in the debugger, I observe the following:

  1. Assert.Throws correctly invokes the ExecuteScalar<T> extension method.
  2. The parameter values are null, as expected.
  3. ExecuteScalar<T> tests its parameters for null values.
  4. The debugger does hit and execute the line containing throw new ArgumentNullException(...).
  5. After executing the throw, control of the application is not immediately transferred to Assert.Throws. Instead, it continues on the next line in ExecuteScalar<T>.
  6. As soon as the next line of code executes, the debugger breaks, and displays the error "Argument null exception was unhandled by user code."

The source code that isolates this behavior is given below.

THE EXTENSION METHOD

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

THE TEST CASES

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

The net effect is that the tests fail when they shouldn't. To the best of my understanding, Assert.Throws<T> should catch my exception and the test should pass.

UPDATE

I took Hans' advice and checked the Exceptions dialog. I wasn't breaking on thrown exceptions, but I was breaking on unhandled user exceptions. Apparently, that's why the debugger breaks into the IDE when the exception is thrown. Clearing the checkbox fixed the problem, and Assert.Throws<T> picked it up. However, if I haven't done this, I can't just press F5 to continue execution, or the exception will become a NullReferenceException.

So now the question is: Can I configure exception breaks on a per-project basis? I only want to do this when I'm testing, but not in general.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

站稳脚跟 2024-10-20 08:58:53

实际发生的情况是,Assert.Throws 确实捕获了您的异常,但 Visual Studio 无论如何都会在第一次机会异常上停止。您只需按 F5 即可检查; Visual Studio 将愉快地继续执行。

正如异常帮助程序告诉您的那样,该异常未被用户代码处理。所以我们知道,出于某种原因,Visual Studio 并不将 NUnit 视为用户代码。

在此处输入图像描述

Visual Studio 实际上会以纯文本形式告诉您这一点(如果您知道在哪里查看的话):

在此处输入图像描述

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

在此处输入图像描述

解决方案 1:使用带有调试符号的 NUnit 调试版本。 将使 Visual Studio 将 NUnit 视为用户代码,因此停止将您的异常视为“用户代码未处理”。这并不是一件小事,但从长远来看可能会效果更好。

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

enter image此处描述

PS 我不会考虑完全避免使用 Assert.Throws 的解决方法,但当然有一些方法可以做到这一点。

What actually happens is that Assert.Throws does catch your exception, however Visual Studio stops on the first-chance exception anyway. You can check this by just pressing F5; Visual Studio will happily carry on executing.

As the exception helper tells you, the exception was unhandled by user code. So we know that Visual Studio doesn’t consider NUnit to be user code for some reason.

enter image description here

Visual Studio actually tells you this in plain text, if you know where to look:

enter image description here

There is also evidence of this fact in the stack trace:

enter image description here

Solution 1: Use a debug build of NUnit with debugging symbols. That will get Visual Studio to regard NUnit as user code, and thus stop treating your exceptions as "unhandled by user code". This isn’t trivial, but might work better in the long term.

Solution 2: Turn off the "Enable Just My Code" checkbox in Visual Studio’s debugging settings:

enter image description here

P.S. I’m not considering work-arounds whereby you avoid the use of Assert.Throws<T> altogether, but there are of course ways of doing that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文