Тестовые примеры VS Заявление ASSERTION

В большинстве моих проектов на C++ я активно использовал инструкцию ASSERTION, как показано ниже:

int doWonderfulThings(const int* fantasticData)
{
    ASSERT(fantasticData);
    if (!fantasticData)
        return -1;
    // ,,,
    return WOW_VALUE;
}

Но сообществу TDD нравится делать что-то вроде этого:

int doMoreWonderfulThings(const int* fantasticData)
{
    if (!fantasticData)
        return ERROR_VALUE;
    // ...
    return AHA_VALUE;
}

TEST(TDD_Enjoy)
{
    ASSERT_EQ(ERROR_VALUE, doMoreWonderfulThings(0L));
    ASSERT_EQ(AHA_VALUE, doMoreWonderfulThings("Foo"));
}

Благодаря моему опыту, первые подходы позволили мне удалить так много мелких ошибок. Но подходы TDD - очень разумная идея для работы с устаревшими кодами.

«Гугл» - они сравнивают «ПЕРВЫЙ МЕТОД» с «Идти по берегу в спасательном жилете, плыть по океану без всякой охраны».

Какая из них лучше? Что делает программное обеспечение надежным?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
0
1 344
5

Ответы 5

Я не знаю, на какое именно подсообщество TDD вы ссылаетесь, но шаблоны TDD, с которыми я столкнулся, либо используют Assert.AreEqual () для положительных результатов, либо иным образом используют механизм ExpectedException (например, атрибуты в .NET) для объявления ошибка, которую следует соблюдать.

Нет причин, по которым ваш тестовый пакет не может перехватывать утверждения, подобные тому, что содержится в doMoreWonderfulThings. Это можно сделать, если ваш обработчик ASSERT поддерживает механизм обратного вызова, или ваши тестовые утверждения содержат блок try / catch.

По моему (ограниченному) опыту, первый вариант немного безопаснее. В тестовом случае вы тестируете только предопределенный ввод и сравниваете результат, это работает хорошо, если проверены все возможные граничные случаи. Первый вариант просто проверяет каждый ввод и, таким образом, проверяет «живые» значения, он очень быстро отфильтровывает ошибки, однако влечет за собой снижение производительности.

В Код завершен Стив МакКоннелл узнает, что первый метод может быть успешно использован для фильтрации ошибок в сборке отлаживать. В сборке выпуска вы можете отфильтровать все утверждения (например, с помощью флага компилятора), чтобы получить дополнительную производительность.

На мой взгляд, лучше всего использовать оба метода:

Метод 1 для обнаружения недопустимых значений

int doWonderfulThings(const int* fantasticData)
{
    ASSERT(fantasticData);
    ASSERTNOTEQUAL(0, fantasticData)

    return WOW_VALUE / fantasticData;
}

и метод 2 для проверки крайних случаев алгоритма.

int doMoreWonderfulThings(const int fantasticNumber)
{
    int count = 100;
    for(int i = 0; i < fantasticNumber; ++i) {
        count += 10 * fantasticNumber;
    }
    return count;
}

TEST(TDD_Enjoy)
{
    // Test lower edge
    ASSERT_EQ(0, doMoreWonderfulThings(-1));
    ASSERT_EQ(0, doMoreWonderfulThings(0));
    ASSERT_EQ(110, doMoreWonderfulThings(1));

    //Test some random values
    ASSERT_EQ(350, doMoreWonderfulThings(5));
    ASSERT_EQ(2350, doMoreWonderfulThings(15));
    ASSERT_EQ(225100, doMoreWonderfulThings(150));
}

Оба механизма имеют ценность. Любая достойная тестовая среда в любом случае поймает стандартный assert (), поэтому тестовый запуск, который приводит к сбою assert, приведет к провалу теста.

Обычно в начале каждого метода C++ у меня есть ряд утверждений с комментарием '// preconditions'; это просто проверка работоспособности ожидаемого состояния объекта при вызове метода. Они прекрасно вписываются в любую структуру TDD, потому что они не только работают во время выполнения, когда вы тестируете функциональность, но они также работают во время тестирования.

В C++ я предпочитаю метод 2 при использовании большинства фреймворков для тестирования. Обычно это упрощает понимание отчетов об ошибках. Это бесценно, когда тест был написан через несколько месяцев или лет после его написания.

Моя причина в том, что большинство фреймворков тестирования C++ будут распечатывать файл и номер строки, где произошло утверждение, без какой-либо информации о трассировке стека. Таким образом, в большинстве случаев вы будете получать номер строки отчета внутри функции или метода, а не внутри тестового примера.

Даже если утверждение будет перехвачено и повторно подтверждено вызывающей стороной, строка отчета будет с оператором catch и может не находиться где-либо близко к строке тестового примера, которая вызвала утвержденный метод или функцию. Это может сильно раздражать, если заявленная функция могла использоваться несколько раз в тестовом примере.

Однако есть исключения. Например, в тестовой среде Google есть оператор трассировки с заданной областью действия, который будет печататься как часть трассировки в случае возникновения исключения. Таким образом, вы можете обернуть вызов обобщенной тестовой функции с помощью области трассировки и легко сказать, в пределах одной или двух строк, какая строка в конкретном тестовом примере завершилась неудачно.

Другие вопросы по теме