Активы или юнит-тесты важнее?

51

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

dsimcha
источник
4
Мне эта идея очень нравится ... настолько, что я делаю эту тему заданием для моего курса по тестированию программного обеспечения и обеспечению качества. :-)
Макнейл
Другой вопрос: стоит ли вам юнит-тестирование ваших утверждений? ;)
Моджуба

Ответы:

43

Утверждения полезны для того, чтобы рассказать вам о внутреннем состоянии программы . Например, что ваши структуры данных имеют допустимое состояние, например, что Timeструктура данных не будет содержать значение 25:61:61. Проверенные утверждениями условия:

  • Предпосылки, которые гарантируют, что звонящий соблюдает свой контракт,

  • Постусловия, которые гарантируют, что вызываемый абонент сохраняет свой контракт, и

  • Инварианты, которые гарантируют, что структура данных всегда имеет некоторое свойство после возврата из функции. Инвариант - это условие, которое является предварительным условием и постусловием.

Модульные тесты полезны для того, чтобы рассказать вам о внешнем поведении модуля . У вас Stackможет быть непротиворечивое состояние после вызова push()метода, но если размер стека не увеличивается в три раза после его вызова три раза, то это ошибка. (Например, тривиальный случай, когда неправильная push()реализация только проверяет утверждения и выходы.)

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

Там может быть некоторое совпадение в том, что тестируется. Например, Stackпостусловие может фактически утверждать, что размер стека увеличивается на единицу. Но есть пределы тому, что может быть выполнено в этом утверждении: следует ли также проверять, что верхний элемент - это то, что было только что добавлено?

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

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

Оба важны, и имеют свои собственные цели.

[В качестве заключительного замечания об утверждениях: чтобы получить наибольшее значение, вам нужно использовать их во всех критических точках вашей программы, а не в нескольких ключевых функциях. В противном случае первоначальный источник проблемы мог бы быть замаскирован и его трудно обнаружить без нескольких часов отладки.]

Макнейл
источник
7

Говоря об утверждениях, имейте в виду, что их можно отключить одним щелчком мыши.

Пример очень плохого утверждения:

char *c = malloc(1024);
assert(c != NULL);

Почему это плохо? Поскольку проверка ошибок не выполняется, если это утверждение пропущено из-за того, что определено что-то вроде NDEBUG.

Модульный тест (скорее всего) будет просто segfault в коде выше. Конечно, он сделал свое дело, сказав, что что-то пошло не так, или сделал это? Насколько вероятно malloc()провал теста?

Утверждения предназначены для целей отладки, когда программист должен подразумевать, что никакое «нормальное» событие не вызовет срабатывание утверждения. malloc()провал, действительно, нормальное событие, поэтому никогда не следует утверждать.

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

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

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

Тим Пост
источник
3
Вот почему утверждения всегда должны быть истинными утверждениями, а не проверкой ошибок.
Доминик Макдоннелл
@DominicMcDonnell Ну, «должно быть правдой» заявления. Я иногда утверждаю, что нужно обойти причуды компилятора, такие как определенные версии gcc, в которых есть встроенная функция abs (). Важно помнить, что в производственных сборках их все равно следует отключать .
Тим Пост
И здесь я думаю, что производственный код - это то место, которое нуждается в утверждениях больше всего , потому что он находится в производстве, где вы получите входные данные, которые вы не считали возможным. Производство - это место, которое устраняет все самые серьезные ошибки.
Фрэнк Шиарар
@Frank Shearar, очень верно. Вы все еще должны терпеть неудачу при самом раннем обнаруженном состоянии ошибки в производстве. Конечно, пользователи будут жаловаться, но это единственный способ убедиться, что ошибки будут исправлены. И намного лучше получить бла, равный 0, чем исключение памяти для разыменования null несколькими вызовами функций позже.
Доминик Макдоннелл
почему не лучше обрабатывать типичные, но часто необычные (в глазах пользователя) проблемы, чем утверждать, что они никогда не должны происходить? Я не могу понять эту мудрость. Конечно, утверждайте, что генератор простых значений возвращает простое число, что требует дополнительной работы, что ожидается в отладочных сборках. Утверждать что-то, что язык может изначально проверить, просто глупо. Не помещать эти тесты в другой переключатель, который можно отключить через несколько месяцев после выпуска, еще более глупо.
Тим Пост
5

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

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

Существует школа мысли, которая говорит, что если вы «утверждаете» что-то, вы могли бы также написать if/ throwзначимый блок исключений. Этот мыслительный процесс происходит от многих утверждений, помещенных в начале метода, чтобы гарантировать, что все значения находятся в границах. Тестирование ваших предварительных условий является очень важной частью наличия ожидаемого постусловия.

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

Лично я нахожу, что получаю больше пробега с модульным тестированием, чем с опрыскивающими утверждениями, но это из-за платформ, которые я разрабатываю в большинстве случаев (Java / C #). Другие языки имеют более надежную поддержку утверждений и даже «Дизайн по контракту» (см. Ниже), чтобы обеспечить еще больше гарантий. Если бы я работал с одним из этих языков, я мог бы использовать DBC больше, чем модульное тестирование.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

Берин Лорич
источник