То, какова роль, утверждает в программах C++, которые имеют модульные тесты?

Я добавлял модульные тесты к некоторому коду C++ прежней версии, и я столкнулся со многими сценариями, где утверждать внутренняя часть функция будет смещена во время выполненного модульного теста. Общая идиома, на которую я натыкался, является функциями, которые берут аргументы указателя и сразу утверждают, является ли аргумент НУЛЕВЫМ.

Я мог легко двигаться, это отключением утверждает, когда я - поблочное тестирование. Но я начинаю задаваться вопросом, как ли модульные тесты, предполагается, облегчают потребность во времени выполнения, утверждает. Действительно ли это - корректная оценка? Модульные тесты, которые, как предполагают, заменяли время выполнения, утверждает путем случая раньше в конвейере (т.е.: ошибка фиксируется в провальном тесте вместо того, когда программа работает).

С другой стороны, мне не нравится добавлять мягкие сбои для кодирования (например. if (param == NULL) return false;). Время выполнения утверждает, по крайней мере, помогает отладить проблему в случае, если модульный тест пропустил ошибку.

14
задан kiamlaluno 12 September 2011 в 12:23
поделиться

8 ответов

Утверждение времени выполнения, по крайней мере, упрощает отладку проблемы, если в модульном тесте ошибка пропущена.

Это довольно фундаментальный момент. Модульные тесты не предназначены для замены утверждений (которые, IMHO, являются стандартной частью создания кода хорошего качества), они предназначены для их дополнения.

Во-вторых, допустим, у вас есть функция Foo , которая утверждает, что ее параметры действительны.
В модульном тесте для Foo вы можете убедиться, что указываете только допустимые параметры, так что вы думаете, что с вами все в порядке.
Через 6 месяцев какой-то другой разработчик вызовет Foo из какого-то нового фрагмента кода (который может иметь или не содержать модульные тесты), и в этот момент вы: Буду очень благодарен, что вы поместите туда эти утверждения.

18
ответ дан 1 December 2019 в 06:35
поделиться

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

Модульные тесты проверяют, что ваш код работает правильно, если он используется правильно.

Утверждения также обнаруживаются, когда ваш код входит в состояние, которое вы считали невозможным (которое, поскольку считается невозможным, не может быть протестировано модулем).

Интересно, что по крайней мере одна среда модульного тестирования C ++ ( Google Test ) поддерживает тестов на смерть , которые представляют собой модульные тесты, которые проверяют правильность работы ваших утверждений (чтобы вы могли знать свои утверждения выполняют свою работу по обнаружению недопустимых состояний программы).

4
ответ дан 1 December 2019 в 06:35
поделиться

Лично я не склонен использовать утверждения, поскольку, как вы обнаружили, они часто плохо работают с модульными тестами. Я предпочитаю генерировать исключения в ситуациях, когда другие часто используют утверждения. Эти проверки и исключения, возникающие при сбое, присутствуют как в отладочной, так и в выпускной сборках, и я обнаружил, что они часто выявляют вещи, которые «не могут произойти» даже в выпускных сборках (которые часто утверждают, что нет, как они). перекомпилирован часто). Я считаю, что это работает лучше для меня и означает, что я могу писать модульные тесты, которые ожидают, что исключение будет выбрано при недопустимом вводе, а не ожидает срабатывания утверждения.

Многие люди не согласны, см. 1 , 2 , и т. Д. , но мне все равно. Избегание утверждений и использование исключений вместо этого хорошо работает для меня и помогает мне создавать надежный код для клиентов ...

1
ответ дан 1 December 2019 в 06:35
поделиться

Обычно у вас не должно быть возможности отключать утверждения, поскольку они должны улавливать «невозможные ситуации». Если срабатывает утверждение, это должно указывать на ошибку.

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

assert(arg != 0);
if (arg != 0)
    throw std::runtime_error();

Таким образом, если неверный аргумент возникает только при определенных условиях (т.е. вне поля), он все равно будет обнаружен.

Если вы запрограммируете таким образом, вы можете отключить утверждения и написать отрицательные тесты, чтобы убедиться, что неверные аргументы обнаруживаются.

3
ответ дан 1 December 2019 в 06:35
поделиться

Утверждения IMO и модульные тесты - довольно независимые концепции. Ни один из них не может заменить другого.

Утверждения предназначены для того, чтобы гарантировать, что определенные условия / инварианты всегда действительны в течение всего жизненного цикла программы. Или, если быть более точным, чтобы гарантировать, что если такое условие нарушится, мы узнаем об этом как можно скорее, как можно ближе к первопричине проблемы.

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

С помощью модульного тестирования класса нельзя гарантировать, что его среда всегда будет выполнять свою часть контракта в реальных жизненных обстоятельствах. Более того, указанная среда включает будущих разработчиков, которые могут не иметь представления о контракте интерфейса, регулирующем использование этого класса (будь то неявное или тщательно задокументированное). А также множество других программных и аппаратных компонентов, которые могут измениться и / или выйти из строя в любое время неконтролируемым образом разработчиками этой конкретной программы.

3
ответ дан 1 December 2019 в 06:35
поделиться

Если ваш код модульного теста верен, тогда assert - это ошибка, обнаруженная модульным тестом.

Но гораздо более вероятно, что ваш код модульного теста нарушает ограничения модулей, которые он тестирует - ваш код модульного теста содержит ошибки!

Комментаторы отметили, что:

Рассмотрим модульный тест, который проверяет, правильно ли обрабатывает функция недопустимый ввод.

Утверждение - это способ программиста обрабатывать недопустимый ввод путем прерывания программы. Прерывание означает, что программа работает правильно.

Утверждения есть только в отладочных сборках (они не компилируются, если определен макрос NDEBUG ), и важно проверить, что программа делает что-то разумное в сборках выпуска. Рассмотрите возможность запуска модульного теста недопустимых параметров в сборках выпуска.

Если вам нужны оба мира - проверка срабатывания утверждений в отладочных сборках - тогда вы хотите, чтобы ваш встроенный модуль модульного тестирования фиксировал эти утверждения. Вы можете сделать это, предоставив свой собственный assert.h вместо системного; макрос (вы бы хотели, чтобы __LINE__ и __FILE__ и ## expr) вызывал написанную вами функцию, которая может генерировать настраиваемый AssertionFailed при запуске в модульном тесте. Это, очевидно, не захватывает утверждения, скомпилированные в другие двоичные файлы, с которыми вы связываете, но не скомпилированные из источника с вашим настраиваемым утверждением.(Я бы рекомендовал это вместо предоставления собственного abort () , но это еще один подход, который вы могли бы рассмотреть для достижения той же цели.)

10
ответ дан 1 December 2019 в 06:35
поделиться

Я использую утверждения только для проверки того, что «никогда не может произойти». Если срабатывает assert, значит, где-то была допущена ошибка программирования.

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

Однако длина строки аргумента имени файла никогда не должна быть отрицательной. Если это так, значит, где-то есть ошибка. Итак, я мог бы использовать утверждение, чтобы сказать, что «эта длина никогда не может быть отрицательной». (Это просто искусственный пример.)

В случае вашего вопроса, если функция утверждает! = NULL, либо модульный тест неверен и не должен отправлять в NULL, потому что этого никогда не произойдет, либо модуль test действителен и, возможно, будет отправлен NULL, а функция неверна и не должна утверждать! = NULL и должна вместо этого обрабатывать это условие.

3
ответ дан 1 December 2019 в 06:35
поделиться

Здесь две возможности:

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

2) Поведение функции не определено, если вход равен нулю. Следовательно, модульный тест не должен проходить в null - тест тоже является клиентом кода. Вы не можете протестировать что-либо, если нет ничего конкретного, что должно делать.

Третьего варианта не существует: «функция имеет неопределенное поведение при передаче нулевого ввода, но тесты все равно проходят с нулевым значением, на случай, если произойдет что-то интересное». Так что я не понимаю, как «я мог бы легко обойти это, отключив утверждения при модульном тестировании». Конечно, модульные тесты заставят тестируемую функцию разыменовать нулевой указатель, что не лучше, чем отключение утверждения. Вся причина существования утверждений в том, чтобы не допустить чего-то еще худшего.

В вашем случае, возможно, (1) применяется в сборках DEBUG, а (2) применяется в сборках NDEBUG. Так что, возможно, вы могли бы запускать тесты с нулевым вводом только в отладочных сборках и пропускать их при тестировании сборки выпуска.

3
ответ дан 1 December 2019 в 06:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: