Подтвердить что ты не робот

NUnit: Почему не Assert.Throws <T> Catch My ArgumentNullException?

Я отправляю этот вопрос заново по воле уважаемого г-на Джона Скита, который предложил мне разработать простую тестовую программу, которая изолирует и демонстрирует проблему, с которой я сталкиваюсь и пересказываю вопрос. Этот вопрос вырос из этого, поэтому, пожалуйста, простите меня, если все это звучит очень знакомо. Вы можете потенциально собрать дополнительные сведения об этом вопросе из этого.

Проблема, с которой я встречаю Assert.Throws<T> от NUnit 2.5.9. Иногда он не сможет поймать какие-либо исключения, вызванные методом, вызванным TestDelegate. Я приписал это поведение воспроизводимым образом в приведенном ниже коде. (Хотя это может быть, по общему признанию, случай Fails On My Machine ™.

Чтобы воспроизвести ошибку, я создал решение с двумя DLL-проектами С#:

  • Первый содержит один класс с одним общедоступным методом. Этот метод является методом расширения, который инкапсулирует логику, необходимую для создания SqlCommand, заполняет ее параметры и вызывает ExecuteScalar на нем. Этот проект не содержит других ссылок.
  • Второй содержит один класс с двумя методами, которые проверяют, работает ли метод в первой DLL как ожидалось. Этот проект ссылается на первый и включает ссылку на NUnit Framework. Никакие другие сборки не указаны.

Когда я выполняю тесты в отладчике, я наблюдаю следующее:

  • Assert.Throws корректно вызывает метод расширения ExecuteScalar<T>.
  • Значения параметров равны нулю, как и ожидалось.
  • ExecuteScalar<T> проверяет параметры для нулевых значений.
  • Отладчик делает и выполняет строку, содержащую throw new ArgumentNullException(...).
  • После выполнения throw управление приложением сразу не переносится на Assert.Throws. Вместо этого он продолжается на следующей строке в ExecuteScalar<T>.
  • Как только выполняется следующая строка кода, отладчик прерывается и отображает ошибку "Исключение для аргумента 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();
            }
        }
    }
}

ИСПЫТАТЕЛЬНЫЕ СЛУЧАИ

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> должен поймать мое исключение, и тест должен пройти.

UPDATE

Я взял совет Ганса и проверил диалог Исключения. Я не нарушал брошенных исключений, но я нарушал необработанные пользовательские исключения. По-видимому, именно поэтому отладчик ломается в среде IDE при вызове исключения. Устранение флажка устранило проблему, а Assert.Throws<T> взял ее. Однако, если я этого не сделал, я не могу просто нажать F5 для продолжения выполнения, иначе исключение станет NullReferenceException.

Итак, теперь возникает вопрос: могу ли я настраивать разрывы исключений для каждого проекта? Я только хочу это сделать, когда тестирую, но не в общем.

4b9b3361

Ответ 1

Что на самом деле происходит, так это то, что 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 с отладочными символами. Что получит Visual Studio, чтобы рассматривать NUnit как код пользователя, и таким образом перестать обрабатывать ваши исключения как "необработанные по коду пользователя". Это не тривиально, но может работать лучше в долгосрочной перспективе.

Решение 2. Отключите параметр "Включить только мой код" в настройках отладки Visual Studios:

enter image description here

P.S. Я не рассматриваю обходы, из-за которых вы вообще избегаете использования Assert.Throws<T>, но, конечно, есть способы сделать это.