Является ли тестовое покрытие адекватной мерой качества кода?

20

Если у меня есть какой-то код, который покрывает 80% тестов (все тесты пройдены), будет ли справедливо сказать, что он более высокого качества, чем код без тестового покрытия?

Или справедливо сказать, что это более ремонтопригодно?

David_001
источник
2
100% покрытие не означает, что оно было хорошо протестировано. Но 0% означает, что он не был проверен вообще.
Mouviciel
1
Технически нет. Практически да. Опыт научил многих разработчиков и тестировщиков программного обеспечения тому, что когда охват кода достигает примерно 80%, типы дефектов, для которых модульное тестирование является адекватным, начинают выравниваться. Это принцип Парето. В основном, как только вы дойдете до того момента, когда вы охватите 80% кода, независимо от качества ваших тестов, вы, вероятно, протестировали 20% кода, который довольно тщательно вызывает большинство потенциальных проблем. Это не абсолют, а скорее общепринятая мудрость. Вы должны быть более тщательными, если жизнь зависит от ваших испытаний.
Calphool
@JoeRounceville Я не уверен ... Я могу достичь высокого охвата тестированием, не тестируя ничего по-настоящему полезного. Охват просто говорит вам, сколько кода затрагивается набором тестов, а не является ли тесты значимыми.
Андрес Ф.
1
@AndresF. Вот почему я сказал «технически нет, практически да». Люди не идиоты (вообще). Они (вообще) не проверяют только легкие случаи. Таким образом, исходя из опыта , многие магазины останавливаются где-то на 80%, делая (довольно безопасное) предположение, что их люди не дебилы.
Calphool

Ответы:

24

В строгом смысле несправедливо предъявлять какие-либо претензии до тех пор, пока не будет установлено качество тестового набора. Прохождение 100% тестов не имеет смысла, если большинство тестов тривиальны или повторяются друг с другом.

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

Чтобы улучшить свои тестовые схемы, вы можете использовать (1) методы белого ящика, (2) методы черного ящика и (3) тестирование мутаций.

(1) Вот несколько хороших техник «белого ящика», которые можно применить к вашим тестам. Тест whitebox создается с учетом конкретного исходного кода. Одним из важных аспектов тестирования белого ящика является покрытие кода:

  • Каждая функция вызывается? [Функциональное покрытие]
  • Каждое утверждение выполнено? [Охват выписок - как функциональный охват, так и охват выписок очень прост, но лучше, чем ничего]
  • Для каждого решения (например, ifили while), есть ли у вас тест, который заставляет его быть правдой, а другой, который заставляет его быть ложным? [Охват решения]
  • Для каждого условия, которое является соединением (использует &&) или дизъюнкцией (использует ||), имеет ли каждое подвыражение тест, где оно истинно / ложно? [Условие покрытия]
  • Охват цикла: у вас есть тест, который вызывает 0 итераций, 1 итерацию, 2 итерации?
  • Каждый breakиз петли покрыт?

(2) Методы «черного ящика» используются, когда требования доступны, а сам код - нет. Это может привести к качественным испытаниям:

  • Ваши тесты черного ящика охватывают несколько целей тестирования? Вы хотите, чтобы ваши тесты были «толстыми»: они не только тестируют функцию X, но также тестируют Y и Z. Взаимодействие различных функций - отличный способ найти ошибки.
  • Единственный случай, когда вам не нужны «жирные» тесты, это когда вы тестируете состояние ошибки. Например, тестирование на недопустимый пользовательский ввод. Если вы пытались достичь нескольких недопустимых целей тестирования ввода (например, недопустимый почтовый индекс и недопустимый адрес улицы), вероятно, один случай маскирует другой.
  • Рассмотрим типы ввода и сформируйте «класс эквивалентности» для типов ввода. Например, если ваш код проверяет, является ли треугольник равносторонним, тест, использующий треугольник со сторонами (1, 1, 1), вероятно, найдет те же виды ошибок, что и данные теста (2, 2, 2) и (3, 3, 3) найдете. Лучше потратить время на размышления о других классах ввода. Например, если ваша программа обрабатывает налоги, вам потребуется тест для каждой налоговой группы. [Это называется разделением эквивалентности.]
  • Особые случаи часто связаны с дефектами. Ваши тестовые данные также должны иметь граничные значения, такие как значения, расположенные выше или ниже границ задачи эквивалентности. Например, при тестировании алгоритма сортировки вам нужно протестировать пустой массив, массив из одного элемента, массив из двух элементов, а затем очень большой массив. Вы должны рассмотреть граничные случаи не только для ввода, но и для вывода. [Это называется анализом граничных значений.]
  • Другая техника - «Угадай ошибки». Есть ли у вас ощущение, что если вы попробуете какую-то особую комбинацию, вы можете сломать свою программу? Тогда просто попробуйте! Помните: ваша цель - находить ошибки, а не подтверждать, что программа действительна . Некоторые люди умеют угадывать ошибки.

(3) Наконец, предположим, что у вас уже есть много хороших тестов для освещения белого ящика и применены методы черного ящика. Что еще можно сделать? Пришло время проверить ваши тесты . Одним из методов, который вы можете использовать, является Mutation Testing.

При тестировании на мутации вы вносите изменения в (копию) вашей программы в надежде создать ошибку. Мутация может быть:

Изменить ссылку одной переменной на другую переменную; Вставьте функцию abs (); Менять меньше чем больше чем больше; Удалить заявление; Заменить переменную на постоянную; Удалить переопределяющий метод; Удалить ссылку на супер метод; Изменить порядок аргументов

Создайте несколько десятков мутантов в разных местах вашей программы [программа все равно должна будет скомпилироваться для тестирования]. Если ваши тесты не находят эти ошибки, вам нужно написать тест, который может найти ошибку в мутированной версии вашей программы. Как только тест обнаружит ошибку, вы убили мутанта и можете попробовать другой.


Приложение : Я забыл упомянуть этот эффект: ошибки имеют тенденцию к кластеризации . Это означает, что чем больше ошибок вы найдете в одном модуле, тем выше вероятность, что вы найдете больше ошибок. Итак, если у вас есть тест, который не проходит (то есть тест успешен, так как цель состоит в том, чтобы найти ошибки), вы должны не только исправить ошибку, но и написать больше тестов для модуля, используя методы выше.

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

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

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

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

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

ChrisF
источник
7

Код без каких-либо тестов может быть очень высокого качества, читабельным, красивым и эффективным (или просто мусором), поэтому нет, несправедливо утверждать, что код с 80% охватом тестов имеет более высокое качество, чем код без покрытия тестами.

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

Joonas Pulakka
источник
3

Я бы назвал это более рефактабельным. Рефакторинг становится чрезвычайно простым, если код покрыт множеством тестов.

Было бы справедливо назвать его более ремонтопригодным.

Иосип Медведь
источник
2

Я бы согласился на ремонтопригодную часть. Майкл Фезерс недавно опубликовал видео о своем великолепном выступлении под названием « Глубокий синергизм между тестируемостью и хорошим дизайном », в котором он обсуждает эту тему. В своем выступлении он говорит, что отношения - это один из способов, то есть хорошо спроектированный код тестируемый, но тестируемый код не обязательно хорошо спроектирован.

Стоит отметить, что потоковое видео в видео невелико, поэтому его стоит загрузить, если вы хотите посмотреть его полностью.

Paddyslacker
источник
-2

Я задавал себе этот вопрос в течение некоторого времени в связи с «покрытием условий». Так как насчет этой страницы на atollic.com "Почему анализ покрытия кода?"

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

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

При интересном изменении зрения Necker Cube тестовый код теперь тестируется тестируемым кодом!

Дэвид Тонхофер
источник
-3

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

Тестирование одно. Избегание мутации данных - еще один. Такова система типов. Или формальная проверка.

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

Andrea
источник
это только ваше мнение или вы можете как-то это подтвердить?
комнат
2
Тестирование не является гарантией того, что программа выполняет то, что вы намерены.
Андрес Ф.
1
Тогда мне остается задаться вопросом, что такое тестирование
Андреа
@gnat это, конечно, мое мнение. Тем не менее, это говорит о том, что говорит. Я взял Haskell в качестве примера языка, чей компилятор очень строг и дает много гарантий относительно правильности ввода, типов, побочных эффектов, мутации данных. Я взял PHP в качестве примера языка, интерпретатор которого очень снисходителен и у которого даже нет спецификации. Даже при отсутствии тестов наличие всех гарантий от системы типов и эффектов обычно дает приличную степень надежности. Чтобы компенсировать это с помощью тестов, нужно иметь очень полный набор
Андреа
Может быть, я немного спешил, когда писал - я разговаривал по телефону - но я все еще думаю, что в этом есть смысл. Я не хочу ругаться на PHP, но я думаю, что говорить о том, что в сравнении Haskell дает гораздо большую степень надежности, является объективным заявлением
Andrea