как можно проверить качество покрытия кода тестами
О метриках тестирования: code coverage для тестировщиков
Как известно из книги «Путеводитель для путешествующих автостопом по галактике», ответ на главный вопрос жизни, вселенной и всего такого — 42. Процент покрытия кода по линиям на одном из моих проектов — 81, дает ли эта цифра ответ на главный вопрос тестирования «cколько тестов достаточно для определения качества продукта»?
В течении своей работы в айти-сфере и тестировании я видела мало команд и проектов, где тестировщики реально используют code coverage в своей работе. Связано это на мой взгляд с двумя вещами:
1. Тем, что тестируем мы прежде всего требования;
2. Далеко не все понимают, как считать и использовать покрытие.
Интересующимся предлагаю свой взгляд на эти 2 пункта.
Требования vs код
Тестировщик тестирует требования. Даже если их формально нет, есть представление о том, как должна вести себя система. Это и только это важно в конечном итоге.
Но.
Не бывает четких исчерпывающих полных требований, проверив каждое из которых, смело можно сказать, что система будет работать как надо и багов нет.
Пример 1
Приложение пытается сохранить данные в БД (располагается на другом сервере). Есть описание того, как оно должно это делать, в том числе звучит требование, что в случае невозможности выполнить операцию (нет доступа к БД, например), мы должы пытаться это сделать до истечения определенного таймаута, потом выдавать клиенту ошибку.
Что значит невозможно выполнить операцию?
Предположим, тестировщик проверяет сценарий с потерей соединения к БД в процессе работы. Все работает хорошо, но значит ли, что багов нет?
В упомянутом приложении мы посмотрели покрытие кода соответствующих классов — оказалось, что разработчик предусмотрел в коде обработку около 5 исключительных ситуаций.
Это значило, как минимум, следующие случаи:
1. Соединение с сервером БД не может быть установлено;
2. Соединение с сервером БД установлено, выполнение запроса вызвало оракловую ошибку;
3. Соединение с сервером БД было установлено, запрос начал выполняться и завис — тут был баг. Приложение ждало ответа примерно минут 5, потом в логи летел эксепшн и больше оно эти данные записать не пыталось.
Пара остальных не стоило внимания по разным причинам.
В примере требования формально проверено было и 1-м кейсом, но баг был найден после анализа покрытия кода. Можно поспорить, что это пример не о пользе code coverage, а о пользе взаимодействия внутри команды (у разработчика детали имплементации можно было бы узнать заранее или дать ему кейсы на ревью), на самом деле я всегда так делаю но не о всем догадаешься спросить, часто внимание к каким-то вещам привлекают непокрытые блоки кода.
Пример 2
В другой системе, которуя я тестировала, при потере консистентности данных приложение должно было выкидывать соответствующий эксепшн, бросать нотификацию мониторингу и ждать, когда придут люди и спасут его. Тесты покрывали разные случаи возникновения таких ситуаций, все обрабатывалось нормально.
Мы посмотрели код, нужный кусок был покрыт хорошо, но я увидела в другом классе непокрытую область кода, в которой бросался тот же самый event о потери консистентности. При каких условиях — неизвестно, т.к. разработчики его быстро выпилили. Оказалось он был скопипасчен из старого проекта, но никто об этом не помнил. Где это могло стрельнуть- неизвестно, но без анализа кода мы бы это не нашли.
Поэтому пусть тестировщик тестирует требования, но если он смотрит еще и код, может поймать то, что в требованиях не описано и хитрые методы тест-дизайна тоже не всегда найдут.
Покрытие = 80. А качество?
Количество не означает качество. Оценка покрытия кода напрямую не связана с качеством продукта, но связана опосредованно.
На одном отчетном совещании я заявила, что покрытие кода у нас увеличилось до 82% по линиям и 51% по условиям, после чего руководством мне был задан вопрос: «А что это значит? Это хорошо или плохо?» Закономерный вопрос, действительно: сколько надо, чтобы было хорошо?
Некоторые разработчики покрывают свой код, добиваясь 100%. Тестировщику 100% добиваться бессмысленно, начиная с какого-то моменты вы столкнетесь с тем, что физически не можете затронуть этот код интеграционными тестами.
Например, разработчики считают хорошим тоном проверять входящие параметры метода на null, хотя в реально работающей системе таких случаев может и не быть (50% по условиям у нас тогда складывалось в том числе из-за этого). Это нормально, передать туда null извне можно было только до первой проверки, которая собственно эту ситуацию и обработает.
К вопросу об «это нормально»: качественная оценка непокрытого кода и ведет в моем понимании к адекватному использованию code coverege. Смотреть важно то, что вы не покрыли, а не сколько. Если это java-код и методы toString(), equals() или ветви с exception, которые сложно воспроизвести интеграционно, ну так и ладно, пусть будет 80% покрытия реальной бизнес-логики. «Лишний» код многие инструменты умеют фильтровать и не считать.
Если сомнения в белых пятнах все-таки остаются, возможно посчитать общее покрытие интеграционными тестами и юнит — разработчики наверняка учли многое что труднодоступно для интеграционных тестов.
Однако есть одно «но». Что, если покрытие кода низкое? 20%, 30%? Где-то я читала забавный факт, что покрытие 50% и меньше (по линиям и условиям, как мне помнится) означает тот уровень тестового покрытия, при котором результат работы приложения будет такой же, как и при отсутствии тестирования вообще. Т.е. там могут быть баги, может не быть багов, с тем же успехом вы могли его и не тестировать. Другое объяснение — много мертвого кода, что маловероятно.
А у нас нет автотестов
А они и не нужны. Даже если вас уверяют в обратном, некоторые разработчики не в курсе, что покрытие можно считать не только для юнит тестов. Есть инструменты, которые пишут покрытие в рантайме, т.е. ставите специально обученный инструментированный билд, проходите на нем тесты, а он пишет покрытие.
А смысл?
Моя знакомая прекрасная тест-лид задала вопрос: «когда тест-кейсы есть не все, и автоматизация в зачаточном состоянии, имеет ли смысл тратить ресурсы на оценку покрытия кода?» Внедрение новых штук в процесс всегда вызывает у менеджмента определенную боль: время, ресурсы и прочие бренности существования, никакого простора для полета тестировщика-мечтателя.
Разберем по порядку, куда конкретно нужно будет потратить ресурсы, если вы решите попробовать считать code coverage:
Пункты 1 и 2 можно отдать разработчикам, могие из них знакомы-слышали-встречались с общеизвестными тулами и тем более смогут построить собственный билд. Построение отчетов, как правило, делается одной командой в командной строке или автоматически, если вы используете CI (у меня это делал jenkins, он же публиковал отчет).
Самое затратное — это четвертый пункт. Основная трудность тут в том, что для адекватной оценки надо уметь читать код, либо садиться рядом с разработчиком, чтобы он объяснял, что значит этот кусок, и как это воспроизвести. Это требует определенной квалификации от тест-инженера и рабочего времени 1 или 2 человек.
Стоит ли оно того — решать команде и ее руководителям. В проектах, где требования слабо формализованы, либо баги возникают необъяснимым для тестеров образом, возможно это может помочь хотя бы понять направление куда копать.
Еще одна категория — проекты, которые предполагают очень hight-level black box тестирование. Это прежде всего тестирование через UI или внешний API систем, внутри которых содержится куча логики, работающей по своим законам, т.е. извне вы не можете ее затронуть или ей управлять, а значит не можете нормально протестировать. Анализ покрытия в таких проектах создаст аргументированную необходимость переходить к более «низким» уровням тестирования: модульным, покомпонентным, тестированию на заглушках и т.п.
Хорошо работает накопленное покрытие кода в цифрах: на графиках можно увидеть моменты, когда вливается новый код, а тесты еще не подоспели; если уровень покрытия был высоким, потом стал снижаться, но предыдущего уровня так и не достиг — где-то может быть хорошее белое пятно недошедших до тестирования требований, и т.д.
Пожалуй, это все, что я хотела сказать на сегодня.
Как Проверить Покрытие Кода Тестами
Можно установить собственную функцию трассировки, посмотреть на результат ее работы и сделать выводы. То есть, фактически, повторить то, что делает coverage.py. Этот вариант не подходит, поскольку мы имеем ограниченное количество событий: call, line, return, exception. Маленькие частички оператора if мы никогда не увидим.
Проблемы тестирования: почему 100% покрытие кода это плохо
На этом методе и строится работы прототипа библиотеки. С помощью этого метода обходится весь байткод. Чтобы отметить существование опкода вызывается проброшенная ранее функция marker.
Прежде всего нужно установить Import.Hook— здесь все довольно просто. В нем есть Finder, который пропускает неинтересные нам модули, создав для нужных Loader. В свою очередь, этот класс получает байт-код модуля, строки его исходного кода, модифицирует байт-код и возвращает измененный байткод в качестве импортируемого модуля.
Конечно же, каждый из вышеописанных критериев имеет ряд дополнительных пунктов и подпунктов. И в данный список особенностей можно добавлять разнообразные характеристики и наработки, которые были приняты на вооружение именно на вашем проекте.
Способы измерения тестового покрытия
Кроме этого, при максимально детальном анализе проводимой проверки, команда по обеспечению качества запросто может оценить плотность и качество покрытия тестами отдельных частей сборки и/или ее компонентов (ответ на вопрос – в каком объеме и что именно мы протестировали).
Работая по подобной таблице (методике), вы как сотрудник отдела QA можете качественно спланировать свой будущий уровень тестового покрытия.
После этого, идем по приоритетам, и обрабатываем каждую строку фичелиста как в описанном выше разделе с требованиями. Пишем тесты, обсуждаем, согласовываем достаточность. Помечаем статусы, по какой фиче тестов хватает. Получаем и статус, и прогресс, и расширение тестов за счет общения с командой. Все счастливы!
Как можно проверить качество покрытия кода тестами
По каждому требованию, начиная с наиболее приоритетных, готовим тесты. При подготовке анализируем, какие тесты потребуются этому требованию, сколько будет достаточно? Проводим полноценный тест-анализ, а не отмахиваемся «один тест есть, ну и ладно».
Таким образом, наличие теста на требование нам вообще ничего не гарантирует! Что значит в таком случае наша статистика покрытия? Примерно ничего! Придется решать!
Покрытие требований позволяет оценить степень полноты системы тестов по отношению к функциональности системы, но не позволяет оценить полноту по отношению к ее программной реализации. Одна и та же функция может быть реализована при помощи совершенно различных алгоритмов, требующих разного подхода к организации тестирования.
Для более детальной оценки полноты системы тестов при тестировании стеклянного ящика анализируется покрытие программного кода, называемое также структурным покрытием.
Тестирование программного кода (покрытия)
Несмотря на очевидную полноту системы тестов, обеспечивающей этот уровень покрытия, данный метод редко применяется на практике в связи с его сложностью и избыточностью.
В отличие от предыдущего уровня покрытия данный метод учитывает покрытие условных операторов с пустыми ветками. Так, для покрытия по веткам участка программного кода
Покрытие программного кода.
6.3.1. Понятие покрытия
6.3.3. По строкам программного кода (Statement Coverage)
Понятие покрытия. 6.3.3.3. Покрытие по условиям (Condition Coverage)
Если рассматривать тестирование как «проверку соответствия между реальным и ожидаемым поведением программы, осуществляемая на конечном наборе тестов», то именно этот конечный набор тестов и будет определять тестовое покрытие:
Как можно проверить качество покрытия кода тестами
Основываясь на данных этой таблицы, вы сможете спланировать необходимый уровень тестового покрытия, а также оценить уже имеющийся.
Для оптимизации тестового покрытия при тестировании на основании требований, наилучшим способом будет использование стандартных техник тест дизайна. Пример разработки тестовых случаев по имеющимся требованиям рассмотрен в разделе: «Практическое применение техник тест дизайна при разработке тест кейсов»
Тестовое Покрытие Test Coverage.
Покрытие кода (Code Coverage)
Для тестирования потоков управления определены разные уровни тестового покрытия: Тестовое покрытие на базе анализа потока управления
Фундаментом для тестирования потоков управления является построение графов потоков управления (Control Flow Graph), основными блоками которых являются:
Способы измерения тестового покрытия
Как известно, тестовое покрытие – наиболее важная метрика анализа качества проверяемого ПО. Она являет собой весьма ценную структуру, которая выражается в процентном эквиваленте и отображает текущий уровень плотности покрытия проверками, установленные технические требования или исполняемость написанного программного кода.
Как проверить коробку
Действительно ли нужно оценивать?
Все метрики тестирования – пустая трата ресурсов и сил. На всем протяжении этого времени можно анализировать продукт, находить баги, писать автотесты и прочее.
Но все же, какую именно величину несут метрики тестового покрытия, на которые тестировщики порой выделяют значительную часть своего рабочего времени?
Логика тестового покрытия
Как верно оценивать?
Перед тем как проводить работу по внедрению выбранной метрики, нужно понять, как вы будете ее использовать. Сначала ответьте именно на этот вопрос – и вы сразу поймете, как лучше всего поступить.
На практике есть два наиболее масштабных и действенных подхода к оценке степени покрытия продукта автоматическими тестами: методика покрытия установленных предписаний (другими словами, что-то схожее с проверкой черного ящика) и работа с программным кодом (процесс тестирования классического белого ящика).
Деятельность покрытия тестами программного кода, бесспорно, является наиболее важной метрикой для максимального обеспечения качества при работе с тестируемой средой, особенно когда речь заходит о проверке продукта с очень сложной логикой или большим объемом написанного кода.
Работы для проверки кода, как правило, выполняются с помощью широкого перечня доступного функционального инструментария, благодаря которому можно качественно отследить, какие ветки программного кода были проверены, а какие остались незамеченными при проведении автотестов.
Так, особой популярностью пользуются следующие инструменты:
Именно благодаря анализу покрытия кода можно создать «плотную» базу автоматических тестов для проверки программного компонента, который был передан в отдел QA для тщательнейшего анализа и проверки. При создании базы автотестов мы можем точно оценить плотность покрытия исполняемого кода тестируемой утилиты (ответ на вопрос – какой именно объем тестирования выполняют автотесты).
Процесс покрытия тестами
Кроме этого, при максимально детальном анализе проводимой проверки, команда по обеспечению качества запросто может оценить плотность и качество покрытия тестами отдельных частей сборки и/или ее компонентов (ответ на вопрос – в каком объеме и что именно мы протестировали).
То есть, активно применяя данную методику, мы сразу поймем, для каких проверок нужно создавать новые тестовые случаи, а для каких – просто удалить повторяющиеся проверки, что значительно сэкономит время для финального релиза, а также уменьшит бюджет на разработку. Ну и качество кода соответственно повысится.
Работа по покрытию кода тестами – занятие, что ярко показывает, насколько в процентном соотношении написанный программный код был проверен.
Методика покрытия программного продукта тестами по праву считается одной из самых первых методик для глобального систематического тестирования. Отчет о ее зарождении ведется с 1963 года, когда в публикацию попали первые материалы, касающиеся правильности составления документации для проверки технических продуктов.
Виды измерения покрытия продукта тестами
На сегодняшний день существует сразу несколько типов измерения тестового покрытия, базовыми из которых являются:
Во время оценки качества покрытия тестами программного кода производится работа с применением специальных настроек, утилит и даже целых библиотек, которые запускаются и функционируют в однородной среде, чтобы максимально плотно и качественно проверить выполняемость, работоспособность и качество любой из написанных и внедренных функций, логик и технических возможностей.
Подобная работа не только положительно сказывается на общей работоспособности разрабатываемого продукта, но также позволяет разработчикам проверить целостность функций и строк кода, которые при нормальной работе ПО редко используются или играют сугубо второстепенные роли.
Компании по контролю качества могут применять итоги проверки для максимально плотного покрытия разработки вспомогательными тестами или тестовыми данными.
Использование на практике
Традиционно, написанный код подкрепляется тестами, призванными, по идее, всегда выполняться.
Подобный отчет позволяет провести максимально качественный анализ для выяснения, есть ли в продукте невыполняемые области кода. Если да, то совокупность тестового покрытия расширяется и все записанные проверки прогоняются повторно.
Базовая цель данного подхода – получить максимально широкий набор тестов для проведения процедуры регрессивного тестирования, в процессе которого тщательным образом проверяется функциональность каждой строчки написанного кода.
Планка покрытия составленного кода тестами выражается соотношением процентов готового и протестированного функционала.
К примеру, «мы провели тест на 60% кода». Смысл данного высказывания заключается в том, как критерий оценки продукта был использован. Например, 60% тестирования путей будет лучше, чем 60% работы операторов.
Процедура покрытия разработки тестами по своей сути является процессом проверки white box.
Проверяемое ПО собирается в одной виртуальной среде, запускается с целью нахождения недочетов или ошибок. Такая методика позволяет разработчикам и QA найти части платформы, которые при адекватном воспроизведении платформы используются весьма редко или вообще никогда не применяются.
На практике, все инструменты и подключаемые библиотеки, применяемые для максимального покрытия кода, требуют существенных затрат по общей производительности, которые в принципе недопустимы при обыкновенном функционировании программного обеспечения. А значит, применяться они могут исключительно в специализированных условиях.
Управленческое тестирование
Разработка тестов на основе управления – наиболее распространенная методика тестирования пресловутого white box, которая разработана на базе логики выполнения программного кода и разработки выполняемых тест-кейсов для максимального покрытия этих путей.
Основой для данного подхода считается разработка графиков потоков управления, внутри которых основными частями считаются:
Для проведения тестирования потоков управления QA используют следующие уровни проверяемого покрытия:
Планка | Наименование | Краткая характеристика |
---|---|---|
Нулевой уровень | — | Тестируем все то, что видим, а остальное пусть тестирует клиент |
Первый уровень | Работа с операторами | Каждый оператор должен выполняться минимум один раз |
Второй уровень | Работа с альтернативными частями (ветви) | Альтернативный узел должен выполняться минимум один раз |
Третий уровень | Работа с условиями | Есть возможность создать условия и альтернативы для каждого проверяемого случая |
Четвертый уровень | Работа с множественным числом условий | Полная выполняемость альтернатив, условий и логики альтернатив |
Пятый уровень | «Бесконечное» число путей | Даже если число путей достигает бесконечного исчисления, они все равно должны выполняться |
Шестой уровень | Работа с путями | Каждый путь должен быть тщательно проверен |
Работая по подобной таблице (методике), вы как сотрудник отдела QA можете качественно спланировать свой будущий уровень тестового покрытия.
Итак, в завершение анализа работы над «идеальным» тестовым покрытием можно выделить аспекты, на которые всегда стоит обращать внимание:
Конечно же, каждый из вышеописанных критериев имеет ряд дополнительных пунктов и подпунктов. И в данный список особенностей можно добавлять разнообразные характеристики и наработки, которые были приняты на вооружение именно на вашем проекте.
Каждая разработка – уникальна и требует максимально индивидуального подхода к процессу создания полноценного тестового покрытия. И пока еще не придумали универсального сборника по разработке мультифункционального тестового покрытия.
Но каждый уважающий себя тестировщик должен четко понимать, с какой стороны подходить к проверке той или иной системы. Он должен оперативно распоряжаться доступным функционалом и предоставленными ресурсами, а также сосредотачивать свою работу на проверке наиболее уязвимых частей разрабатываемого продукта.
Code Coverage — хочу верить
Разработчик обязан знать свои инструменты! Знание инструментов увеличивает продуктивность, эффективность, производительность, потенцию разработчика! Не могу программировать без R#!
Подобного рода фразы можно услышать от абсолютно разных людей: фанатиков разработки, продавцов различных утилит, пользователей удобных тулз. Слышит их и мой менеджер, когда мне хочется поэкспериментировать с чем-то новеньким.
Правда, инструкция к инструменту обычно не содержит раздел «Противопоказания», не указываются ситуации когда НЕ стоит применять утилиту. Между тем, подобный раздел мог бы сэкономить тонны времени на неудачные эксперименты.
Сегодня я пошвыряю камни в огород Code Coverage (CC). Достаточно полезная метрика, под которой лежат несколько скудно документированных граблей.
«Есть ложь, есть наглая ложь
есть статистика».
Создатели SonarQube это великолепно понимают, недаром у SonarQube с десяток CC метрик. Я буду перебивать статистику используя CC от DevExpress, там метрика лишь одна.
Проблема 1. Типичный тест не того. Протестируем метод с кучей проверок аргумента:
Метод покрыт тестами на 83%, чего обычно достаточно для авто-билда. Технически спорить не о чем, большая часть кода покрыта тестами, но основной сценарий тестами не затронут. Тестами покрыта наиболее простая часть кода, не наиболее важная.
Проблема 2. Замеряем актуальный код, вместо необходимого.
Тестируемый метод не содержит проверки на null аргумент, однако покрытие — 100%. Иногда люди забывают: Code Coverage — это метрика покрытия кода, не метрика закрытия требований; если в методе не достает логики (метод недостаточно сложен для решения своей задачи) — CC это не покажет.
100% покрытия не гарантируют работоспособности программы. Доводя до абсурда: пустой метод элементарно покрывается на 100%. Непустой метод покрывается на 100% тестами без Assert-ов.
Проблема 3. Оптимизм. Немного иное проявление предыдущей проблемы. Как видно, один тест покрывает 100% кода. Попробуем переписать наш метод, избавившись от LINQ (для улучшения производительности).
Получаем лишь 73% покрытия. Функциональность не изменилась, метрика упала. Мало того, что 100% покрытия не гарантируют работоспособности программы, эти 100% могут быть фейковыми. Вывод: LINQ — г**но результаты CC могут быть завышены, старайтесь проверять покрытие в редакторе.
Следствие: Используемый инструмент может не обладать всей желаемой функциональностью. Тривиальная вещь, не менее от того верная.
Проблема 4. Передача ответственности.
Метод покрывается на 100% одним тестом. При этом покрытие OuterLib библиотеки лежит на совести того, кто её добавил. Или обновил. Года три назад, до введения CC. До увольнения.
Приходится снова констатировать факт: мало того, что 100% покрытия не гарантируют работоспособности программы, эти 100% могут быть фейковыми.
Помимо чисто кодовых моментов есть несколько претензий именно к обработке результатов CC
Претензия 0, всем известная. 100% покрытия. Нет 100% покрытия — нет одобрения билда. Проблема в том, что первые проценты покрытия получить относительно просто, а вот последние… Особенно, когда часть кода генерируется. Или недостижима (поскольку создана для Васи, который будет её юзать через два дня). Или просто теоретически достижима, а пример подбирать\высчитывать пару недель (такое бывает при работе с математикой). Короче, большинство команд (из тех кто вообще интегрирует CC в CI) останавливаются на 60\70\80 процентах необходимого покрытия.
Претензия 1, спорная. Покрытие мертвого кода. На моей памяти схожая проблема особо ярко проявилась в ходе проверки Mirand-ы коллегами из PVS. Комментарии довольно эмоциональны, но часть споров касалась мертвого кода: часть найденных диагностик указывала на (заброшенные) плагины, но не на ядро.
Возникает вопрос: нужен ли CodeCoverage для мертвого кода? С одной стороны, мертвый код это проблема, и привлечение внимания к нему приветствуется. С другой стороны, мертвый код не влияет на продакшн, так стоит ли позволять ему влиять на CC метрику?
Претензия 2. Важность кода. Расширение проблемы 1. В моем проекте есть два примечательных контроллера: «оплата» и «переговорка». «Оплата» критична для клиента, и я вполне согласен с требованием «80% покрытия», «переговоркой» же пользуются 1.5 анонимуса. В год. И она не менялась уже два года. Вопрос: для чего писать тесты к полумертвой функциональности? Лишь для получения 80% бейджа одобрения автосборки?
Претензия 3, невозможная. Метрика как ачивка. Это когда никто не проверяет что именно покрыто. Помните байки про оплату за линии кода? Мне доводилось слышать про людей, которые творили ненужный кода для лучшего покрытия.
Претензия 4. Метрика «за бесплатно». Когда руководство скидывает требование «покрывайте код на 80%», и разработчики безропотно соглашаются. Проект при этом — одноразовый. Или прототип. Или дедлайн на носу. Или имеется здоровенный макаронный легаси монстр без единого теста.
Покрытие кода тестами требует времени! Если покрытие еще и замерять — время на тесты может и возрасти (хотя может и упасть). Так что если команда не успела сдать проект в срок, но зато достигла 80% покрытия — вина может поделиться между руководством и разработчиками. Вопрос линии раздела вины поднимать не стоит, ибо холивар.
Под конец. Еще раз замечу: СС — метрика полезная, хоть и с сюрпризами. Она реально помогает с контролем кода, если нет слепого стремления к цифрам в отчетах.