На главную

Debug.Assert и модульные тесты

14-09-2018

А я то думал, что знаю всё. На днях писал тестовый проект и зафиксировал важную особенность входного массива с помощью Debug.Assert.

public static IEnumerable<(int, int)> ReadIntPairs(TextReader reader)
{
    var line = reader.ReadLine();
    while (line != null)
    {
        var numbers = line.Split(' ');
        Debug.Assert(numbers.Length == 2);

        yield return (int.Parse(numbers[0]), int.Parse(numbers[1]));

        line = reader.ReadLine();
    }
}

Здесь я читаю из входного потока пары целых чисел. Числа разделены пробелом, в одной строке одна пара. По условиям задачи данные всегда поступают на вход в правильном формате и проверять их дополнительно не надо.

Как раз в том случае, когда проверка не нужна, надо вызывать Debug.Assert. Он позволяет передать программисту-читателю дополнительную информацию об окружении метода. Вы можете проверять переменную на равенство null, когда она должна иметь значение. Вы можете устанавливать ограничения на размер массива. В моём коде я проверяю, что в строке действительно два числа и даю неявную подсказку тому, кто будет разбирать этот код позже.

В тесте я решил проверить, что случится, если в строке будет не два числа, а одно:

public void ReadIntPairs_WithSingleNumberInsteadOfPair_ThrowsException()
{
    using (var reader = new StringReader("3 100\n5\n4 700\n"))
    {
        var intPairs = Program.ReadIntPairs(reader)
                              .ToArray();
    }
}

Обычно в названии метода я пишу, какое исключение будет создано: суффикс тестовых методов имеет вид ThrowsArgumentNullException или ThrowsInvalidOperationException. Но в данном случае я просто не помню, какой тип исключения выбросит Debug.Assert и оставляю название метода неполным. Запущу и всё увижу.

Однако при запуске перестали выполняться все тесты. Модуль, который их запускает, сам завершается с ошибкой:

Активный тестовый запуск прерван. Причина: Assertion Failed.

На английском:

The active test run was aborted. Reason: Assertion Failed.

Похожее сообщение об ошибке вы увидите, если используете xUnit вместо MSTest. Кажется, что дело в Debug.Assert, но почему? Разве модуль запуска тестов не должен просто перехватить и обработать исключение?

Оказывается, нет.

Это поведение by design. Если вы ожидаете, что на вход вашей функции попадут неправильные значения, явно проверьте их, и выбросьте исключение, унаследованное от ArgumentException. Но если вы ожидаете правильные значения, которые удовлетворяют неочевидным условиям, применяйте Debug.Assert. Это поможет Visual Studio и Resharper не ругать ваш правильный код.

В моём случае, поскольку условия задачи гарантировали правильные данные, я оставил конструкцию Debug.Assert и удалил тест. У вас может получиться наоборот: тест придётся оставить, а Debug.Assert заменить на генерацию исключения.