Как стать автором
Обновить

Комментарии 39

Так а почему 100% покрытие это плохо-то?
> слепо верить отчетам нельзя
> coverage в 100% расслабляет команду

В общем, достаточно прочитать статью :)

По-моему вы не на то вину сваливаете. Как хорошее покрытие тестами может быть виновно в том, что команда безответственно работает? Может снятие тестов с куска кода каким-то магическим способом повышает ответственность у безответственных?
Я думаю имеется в виду тот факт, что если система тестирования кода сообщает о 100% покрытии кода тестами это не значит, что они действительно покрыты на 100%. Т.е. даже если вы думаете, что покрыли код тестами на 100% и система вам говорит об этом в нём всё ещё могут быть ошибки.
Впрочем, плохо не само покрытие кода тестами на 100%, а слепая уверенность в том, что это так.

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

На сколько я понял, речь шла о модульном тестировании, а не об интеграционном, так что мне кажется, ты немного не в тему (могу ошибаться)

Если бы ConstantineDrozdov не хамил, то получил бы разъяснение, почему в данной конкретной ситуации он ошибается.
Так в том и дело, что статью я прочитал, а про 100% покрытие увидел только эти две фразы :)
Почему coverage.py работает плохо понятно, почему никаким тулзам нельзя верить на 100% думаю все и так знают, так что нам теперь, специально уменьшать покрытие тестов? Ну чтоб это, не расслаблялись.
Объективно просто статья не соответствует заголовку.
Да, статья вполне вменяемая, в ней есть адекватные мысли, но по существу заголовок ее реально дизориентирует.
Тут суть на самом деле в том что качество тестов не равно показателю покрытия кода.
При выборе между сотней «assert true» без параметров и всего парой тестов, грамотно покрывающих критический функционал всегда для продукта будет лучше второе.
Кроме того, остальные выводы также крайне очевидны. В других комментариях уже обращали внимание на конкретику, скажу лишь что многие утверждения тут действительно уровня «воздух — прозрачный, водя — мокрая, а огонь — горячий».
Если честно, я ожидал действительно грамотного и интригующего ответа на поставленный в заголовке вопрос с примером реального кода в котором творится какая-то магия языка, не поддающаяся нормальным тестам или еще какою-то загадку.
Если честно, я ожидал действительно грамотного и интригующего ответа на поставленный в заголовке вопрос

Вот я о том же и говорю, это довольно хорошая статья о том, как внутри работают описанные инструменты, и почему они могут ошибаться. Но слишком желтый заголовок.
В статье, по сути, 99% воды. Достаточно просто прочитать капитанское «заключение»…
100% покрытие это ни плохо и ни хорошо. Это просто число. Но если вы хотите использовать его как цель — вспомните про Закон Гудхарта. О нём, в общем-то, никогда забывать не стоит…

Код картиночками. В джепеге. Серьезно?

if result['Total'] ≥ 1000
Я даже попробовал это запустить.

Шрифты с гигатурами для повышения читаемости кода (считать и спарсить один символ "≥" намного проще, чем два символа, особено в какой-нибудь строке с js-лямбдой), например — https://github.com/tonsky/FiraCode

Похоже это был доклад на каком-то митапе с вот этими слайдами, который трансформировался в хабростатью. Не специально ж они так, надеюсь...

ИМХО, но статья совсем не о том, что 100% покрытие кода это плохо. Скорее «Почему ваш код не покрывается на 100% и как это исправить».
P.S. Картинки, чего-то, полетели

У меня немного философский вопрос. А как вообще физически можно получить 100% покрытие тестами? Всегда ведь есть некая "серая зона", которая зачастую возникает довольно неожиданно, зависит от окружения, поступающих некорректных данных и так далее. То есть, 100% покрытие тестами — это вообще всегда ложь, каким бы методом не достигалась эта цифра. Но если 100% — всегда ложь, то и любой другой процент — тоже автоматически ложь. 100% — это эталон. Лишившись эталона, мы лишаемся всего, приближающегося к эталону. Я не говорю, что тесты не нужны, но когда кто-то говорит о сколько-то процентном покрытии тестами, это звучит как попытка пустить пыль в глаза. я ещё понимаю, когда есть какое-то жёсткое ТЗ, в котором прописано, условно, какая функция при каких условиях какое значение должна возвращать за какое время. Или что-то подобное. Тогда понятно, что такое 100%. Это когда напротив каждого такого пункта в ТЗ стоит галочка — покрыто тестами. Но и не более. То есть, в этом случае, нельзя говорить о полной корректности программы на любых данных в любых условиях. Любой выход за границы, прописанные в ТЗ — швах. А в условиях, когда тестами покрывается нечто, у чего и ТЗ никогда не было — что они вообще тестируют? Понятно, что часто можно примерно прикинуть, какой результат будет верным. Но часто бывает и так, что формально правильно, а по существу — издевательство.

мммм… Вы никогда не видели OpenSource проектов? 100% coverage сплошь и рядом. И некоторые как-то ведь умудряются и на изменения реагировать и релизиться регулярно.

Наличие плашки ещё не говорит о полном покрытии. Достаточно просто спросить: 100% от какой суммы случаев? В основном, это всё конечно полезно, но я просто не вижу никаких доказательных способов заявить, что покрыто реально 100%. Чтобы это заявить, нужно сначала доказать, что успешное прохождение этих тестов полностью гарантирует работоспособность кода в 100% случаев. Но зачастую, вместо доказательства используется тот или иной автоматический анализ. Как они работают — отдельный вопрос. В принципе, статья довольно наглядно иллюстрирует некоторые аспекты. И ни о какой доказательности тут речи не идёт. Можно сказать, что такие средства выявляют некоторые проблемные места, но никто не даст гарантии, что они выявляют 100%. Иными словами, они доказывают, что в конкретном месте — возможна ошибка и её нужно покрыть тестом. Но они не доказывают, что кроме найденного — ничего больше нет. То есть, эти 100% — это 100% от найденного, а не от существующего. В принципе, это уже что-то. Но с другой стороны, это реально не правильная формулировка. Это нельзя использовать как доказательство корректности программы. Посмотрите на различные версии json-парсеров. Они все в теории должны делать одно и тоже. А на практике, они отличаются и выходной результат работы одного парсека может не совпасть с результатом работы другого. При этом, оба могут быть покрыты тестами на 100%, поскольку формально оба алгоритма могут быть корректными. Но при попытке обменяться результатом между двумя системами использующими разные партеры — будет ошибка. Кто виноват? Виноват плохой стандарт, который допускает неопределённое поведение. Соответственно, во вселенной просто физически не может быть ни одного парсера, который не содержит ошибок относительно другого парсера. И не имеет значения, какие у них там покрытия.

100% от какой суммы случаев?

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


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

Вы, по-моему, неправильно понимаете суть тестов. Они даже не пытаются доказать, что программа безошибочна, что проблемных мест в ней нет. Цифры 100%, 99%, 50% показывают, в идеале, что при некоторых наборах данных программа выдаёт нужные результаты и при выполнении тестов затронуто столько процентов кода. Основная функция тестов — фиксация поведения программы в строго заданных случаях, обычно качественно меньших чем все возможные.


Делать даже из реального 100% покрытия кода (а пост о том, что цифре доверять нельзя, как правило она не реальная, завышенная) тестами выводы, что весь код работает правильно логически неверно. Это индуктивное умозаключение практически всегда оказывается неверным хотя бы из-за комбинаторного взрыва количества случаев подлежащих рассмотрению в сколь-нибудь полезной программе. Только в редких случаях может оказаться, что разработчик тестов покрыл если не все возможные сочетания входных параметров, то хотя бы все классы эквивалентности в рамках формальной спецификации языка (в конкретной реализации языка могут быть ошибки!)


Объективной пользы от автотестов по сути две:


  • инструмент, позволяющий разработчику легко проверять исправил ли он баг, возникающий при конкретном сочетании параметров
  • инструмент, позволяющий разработчику легко проверять не сломал ли он исправления багов, возникавших ранее при конкретном сочетании параметров

Автотесты не про выявление новых багов, а про исправление известных, причём только для конкретных случаев. Можно делать индуктивные умозаключения, что если тест показывает, что мы исправили баг, при котором для 2+2 возвращалось 5, и теперь возвращается 4, то и для 3+3 вернется 6, но нужно понимать что тесты этого не гарантируют и, например, для 32767 + 1 код может вернуть -1, и даже для 3+3 может вернуться 9, если в реализации случайно стоит a*b, а не a+b.

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

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

Наверное, описанный вами подход вполне оправдан в каких-то условиях, но это выглядит как способ решения довольно узких задач. В моём представлении, более применимы тесты в стиле «может ли пользователь зарегистрироваться». Тест получается более комплексным. Его можно крутить несколько раз, на разных наборах данных, чтобы проверять пограничные условия или ранее известные баги. А просто проверять, что а+б == б+а — это как-то мне не понятно.

Автотесты — это инструмент проверки того, что тикет не надо повторно открывать, если он уже закрыт. Грубый процесс:


  • заводится тикет на баг или фичу, где описывается ожидаемое поведение
  • пишется тест, который падает если желаемого поведения нет и проходит если есть
  • убеждаемся что тест падает, то есть проблема реально есть, желаемого поведения нет
  • изменяем код до тех пор пока тест не перестает падать, пока не получим желаемое поведение
  • закрываем тикет

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

По сути тесты и являются ТЗ, переведенным с человеческого языка, или сразу написанным на машинном.

Я бы вместо слова "являются" скорее применил бы "должны являться". Но на практике, это далеко не всегда так. В основном, по той простой причине, что в большинстве случаев естественный человеческий язык перевести на строгий однозначный и формализованный язык просто невозможно даже в теории по причине его изначально метафорической природе. Кроме того, зачастую тесты пишутся даже для кода, который написан вообще без ТЗ, просто интуитивно. И эти тесты, как бы они хорошо не покрывали бы код — просто культ карго и дань моде.
Я не являюсь большим специалистом в области автоматического тестирования, но сама идея такого подхода, как описана в статье — мне кажется просто каким-то маркетинговым трюком для коммерческого продукта и не более того. Описанный там подход наверное полезен, для статического анализа кода и выявления в нём потенциальных проблемных мест. Но писать тесты на основе этого анализа — довольно странное занятие, на мой взгляд. От фактических ошибок такие тесты не избавят. Выше я упомянул пример с json-парсерами, которые как ни покрывай, а они всё равно будут в ряде случаев просто не совместимы друг с другом.

А тут у меня сразу по первому примеру вопрос, где сравниваются длины массивов — где там 100% покрытие? Не учтены случаи с пустым массивом, null (я совсем не питонист, не знаю какая верная формулировка), с массивами большой длинны(что-то около Long.MAX_VALUE), вызов функции без аргументов, аргументы не являются массивами.

Тест — не только проверка функциональности, но и контракт между разработчиками.

Не путайте покрытие кода тестами с покрытием кодом кейсов использования.

Эмм… даже растерялся немного. А зачем тогда такой код, который не покрывает тесты?

Код покрывает кейсы, а не тесты. Грубо, задача сложить два числа, код типа sum(a, b) { return a + b; } тест assert(4, sum(2,2)); 100% покрытия кода тестами, но код не покрывает все возможные кейсы использования, например, приводящие к переполнению целых чисел или передаче вообще не чисел.

Ай, а вот и нет и это распространенная ошибка.

Да с формальной точки зрения покрытие — 100%. Но вы все же кое-что забыли. Какого типа переменные a,b? В сигнатуре не указано.
А значит покрытие нужно увеличить до нескольких вариантов входных типов:

assert(?, sum(«2», «2»));
assert(?, sum('2', '2'));
assert(?, sum(2.00, 2.00));

И это минимум того что нужно сделать.

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

Кому нужно это сделать? Своим тестом я показал, что функция принимает целые числа. По контракту, по документация в иных случаях неопределенное поведение. Я не закладывал в функцию передачу иных типов. А может закладывал, но в контракт не хочу включать, оставляя за собой право на изменение без потери обратной совместимости, заменив неопределенное поведение на определенное. Может исключение буду бросать, может к целому приводить, может к вещественному, а может выбирать ещё что-то. Но сейчас в моем контракте ничего кроме простейшего сложения целых нет.

Зачем было лить столько воды, чтобы донести мысль: «100% покрытия кода» не равно «100% работоспособности кода»?
Вот, например, 100% покрытый код:
def foo (a, b):
    return a+b

assert foo(2,3) = 5

Который 100% не до конца протестирован.

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

До вас мысль не донесли. 100% покрытия кода от какой-то конкретной тулзы вполне может не означать, что тесты реально покрывают каждую ветвь кода. Работоспособность дело десятое, пост не про неё :)

Правильно бы было сформулировать «почему 100% покрытие кода не является 100%-м покрытием кода»
На правах автора доклада добавлю свои 5 копеек)

Основная мысль доклада, как уже говорили в комментариях — «Не надо верить тулзам, которые говорят, что тесты хорошие».
coverage.py по-дефолту покажет непокрытые строки кода и вы увидите заветные 100% покрытия, но стоит указать параметр --branch и покрытие падает, потому что вряд ли покрыты все возможные переходы. Покрыв переходы между инструкциями (этот режим считает именно переходы между statements, а не lines) получаем снова 100%.
Но получается и этим 100% верить нельзя. Вот отсюда и второстепенная мысль доклада — «А что можно сделать, что бы еще лучше оценить покрытие кода». И во второй половине доклада представлена идея покрытия кода на уровне байткода.

Что касается названия — реакция в комментах показала, что название выбрано как нельзя лучше :-)
Для Java есть библиотека мутационного тестирования http://pitest.org/
Суть в том что с помощью инструментирования байткода по определенным правилам (например инвертирование условия в операторе if, замена тела метода на return null и т.д.) изменяется тестируемая система и если при этом не падает ни один тест, то их явно недостаточно. После прогона тесткейса на всех мутациях будет собран настоящий честный code coverage
Да, мутационное тестирование это очень интересная идея для проверки качества тестов.
Для Python есть несколько библиотек, которые я смотрел:
cosmic-ray
mutpy
Они работают, но когда у вас много разветвленного кода — количество мутантов растет и прогон тестов начинает занимать непростительно много времени.
О мутационном тестировании я рассказывал на Pycon Siberia 2016. К сожалению, видео доклада пока нет, но организаторы обещали.

После прогона тесткейса на всех мутациях будет собран настоящий честный code coverage

С этим можно поспорить) Безусловно, после прогона тестов на мутантах и исправления тестов — ваши тесты станут лучше. Но, как мне кажется, говорить о честном coverage, все еще рано.
Помимо покрытия по стркам (SC) и веткам (DC) есть еще полное покрытие условий в ветке (MC/DC).
Однако 100% покрытие кода говорит только о том, что команда тестировщиков добивалась 100% покрытия кода. О качесте тестирования это не говорит вообще:
1. Создание тестовых ситуаций не гарантирует что какие-либо выходые значения проверялись.
2. Если в функции есть две ветки которые тестировали независимо, то результаты их покрытия будет 100% (это покажет любой сборщик). Но при этом элементарно создать комбинацию, которая всё повалит.

int func(int a, int b)
{
    int div = 1;
    if (a) div = 0;
    if (b) return (100/div);
    return div;
}

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

woooody прав. Для того, что бы говорить о покрытии кода тестами, нужно обязательно уточнять какая именно метрика используется. Если интересно — вот достойное описание различных видов метрик покрытия кода. Но… их тоже нужно применять в зависимости от того, что вы собственно разрабатываете. Вот рекомендации.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий