Тестовые случаи против утверждения 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 - очень умная идея для обработки устаревших кодов.

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

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

19.08.2008 23:19:18
5 ОТВЕТОВ

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

0
19.08.2008 23:23:51

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

1
19.08.2008 23:31:17

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

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

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

Способ 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));
}
4
20.08.2008 00:30:52

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

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

2
22.08.2008 22:24:14

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

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

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

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

0
27.08.2013 21:01:59