Русский
Русский
English
Статистика
Реклама

Юнит-тестирование

Аспекты хороших юнит-тестов

02.05.2021 12:15:03 | Автор: admin

Эта статья является конспектом книги Принципы юнит-тестирования.

Давайте для начала перечислим свойства хороших юнит-тестов.

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

Второе. Тестируют только самые важные части вашего кода. Не весь рабочий кодзаслуживает одинакового внимания.

Третье. Дают максимальную защиту от багов с минимальными затратами на сопровождение. Для этого нужно уметь распознавать эффективные тесты и писать их.

Однако распознавание и написание эффективного теста два разных навыка. И для приобретения второго навыка необходимо сначала освоить первый. Далее в этой статье будет показано, как распознать эффективный тест. Также будет рассмотрена пирамида тестирования и тестирование по принципу черного ящика / белого ящика.

Четыре аспекта хороших юнит-тестов

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

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

Начнем с первого атрибута хорошего юнит-теста: защиты от багов. Баг (или регрессия) это программная ошибка. Как правило, такиеошибки возникают после внесения изменений в код.

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

Для оценки того, насколько хорошо тест проявляет себя в отношении защиты отбагов, необходимо принять во внимание, следующее:

  • объем кода, выполняемого тестом;

  • сложность этого кода;

  • важность этого кода с точки зрения бизнес-логики.

Как правило, чем больше кода тест выполняет, тем выше вероятность выявить в нембаг. Само собой, тест также должен иметь актуальныйнабор проверок (assertions).

Важен не только объем кода, но и его сложность и важность с точки зрения бизнес-логики. Код, содержащий сложную бизнес-логику, важнее инфраструктурного кода ошибки в критичной для бизнеса функциональности наносят наибольший ущерб.

Как следствие, тестирование тривиального кода обычно не имеет смысла. Этот кодслишком простой и не содержит сколько-нибудь значительного объема бизнес-логики.

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

Рефакторингом называется модификация существующего кода без изменения его наблюдаемого поведения. Обычно рефакторинг проводится для улучшения нефункциональныххарактеристик кода: читаемости и простоты. Примеры рефакторинга переименованиеметода или выделение фрагмента кода в новый класс.

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

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

Частые ложные срабатывания могут привести к следующим ситуациям:

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

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

Что приводит к ложному срабатыванию?

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

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

Рис. 1 Тест слева связан с наблюдаемым поведением SUT, а не с деталями реализации. Такой тест более устойчив к рефакторингу, чем тест справаРис. 1 Тест слева связан с наблюдаемым поведением SUT, а не с деталями реализации. Такой тест более устойчив к рефакторингу, чем тест справа

Связь между первыми двумя атрибутами

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

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

Ситуация, когда тест проходит, а тестируемая функциональность работает правильно, называется истинным отрицательным срабатыванием: тест правильно определяетсостояние системы (отсутствие в ней ошибок).

Рис. 2 - Отношение между защитой от багов и устойчивостью к рефакторингуРис. 2 - Отношение между защитой от багов и устойчивостью к рефакторингу

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

С другой стороны, существует симметричная ситуация: функциональность работаетправильно, но тест сообщает об ошибке. Это ложное срабатывание. И с ней помогает устойчивость к рефакторингу.

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

  • насколько хорошо тест выявляет присутствие ошибок (отсутствие ложноотрицательных срабатываний, сфера защиты от багов);

  • насколько хорошо тест выявляет отсутствие ошибок (отсутствие ложных срабатываний, сфера устойчивости к рефакторингу).

Как видно из формулы на рис. 3, улучшить точностьтеста можно двумя способами. Первый повышение числителя (сигнал), то естьповышение вероятности выявления ошибок. Второй уменьшение знаменателя(шум), то есть уменьшения вероятности ложных срабатываний.

Рис. 3 Формула точности тестаРис. 3 Формула точности теста

Третий и четвертый аспекты: быстрая обратнаясвязь и простота поддержки

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

Простота поддержки оценивает затраты на сопровождение кода. Метрика состоит из двух компонентов:

  • Насколько сложно тест понять. Этот компонент связан с размером теста. Чемменьше кода в тесте, тем проще он читается и проще изменяется при необходимости.

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

В поисках идеального теста

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

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

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

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

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

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

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

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

Рис. 4 - Тривиальный тест, покрывающий простой фрагмент кодаРис. 4 - Тривиальный тест, покрывающий простой фрагмент кода

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

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

Рис. 5 Места, которые занимают тесты по отношению друг к другуРис. 5 Места, которые занимают тесты по отношению друг к другу

Четвертый атрибут простота поддержки не так сильно связан с первыми тремя, за исключением сквозных (end-to-end) тестов. Сквозные тесты обычно имеют больший размер из-за необходимости подготовки всех зависимостей, к которымимогут обращаться такие тесты. Они также требуют дополнительных усилий дляподдержания этих зависимостей в работоспособном состоянии. Таким образом,сквозные тесты требуют больших затрат на сопровождение.

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

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

Рис. 6 Компромиссы между атрибутами хорошего тестаРис. 6 Компромиссы между атрибутами хорошего теста

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

Компромисс между первыми тремя атрибутами хорошего юнит-теста напоминает теорему CAP. Эта теорема утверждает, что распределенное хранилище данных не можетпредоставить более двух из трех гарантий одновременно: согласованность (consistency) данных, доступность (availability), устойчивость к разделению (partition tolerance).

Сходство является двойным:

1. В CAP вы тоже можете выбрать максимум два атрибута из трех;

2. Устойчивость к разделению в крупномасштабных распределенных системахтакже не является предметом для компромиссов. Большое приложение такоекак, например, веб-сайт Amazon не может работать на одной машине. Вариантс достижением согласованности данных и доступности за счет устойчивостик разделению просто не рассматривается.

Пирамида тестирования

Концепция пирамиды тестирования предписывает определенное соотношениеразных типов тестов в проекте: юнит-тесты, интеграционные тесты, сквозные тесты.

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

Рис. 7 - Пирамида тестирования предписывает определенное соотношение юнит-,интеграционных и сквозных тестовРис. 7 - Пирамида тестирования предписывает определенное соотношение юнит-,интеграционных и сквозных тестовРис. 8 - Разные типы тестов в пирамиде принимают разные решения относительно быстрой обратной связи и защиты от баговРис. 8 - Разные типы тестов в пирамиде принимают разные решения относительно быстрой обратной связи и защиты от багов

Точное соотношение между типами тестов будет разным для разных команд и проектов. Но в общем случае должно сохраняться соотношение пирамиды: сквозныетесты составляют меньшинство; юнит-тесты большинство; интеграционные тестылежат где-то в середине.

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

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

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

Выбор между тестированием по принципу черного ящика и белого ящика

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

Тестирование по принципу белого ящика работает по противоположномупринципу. Этот метод тестирования проверяет внутренние механизмы приложения. Тесты строятся на основе исходного кода, а не на основе требованийили спецификаций.

Рис. 9 - Достоинства и недостатки тестирования по принципу черного ящика и белого ящикаРис. 9 - Достоинства и недостатки тестирования по принципу черного ящика и белого ящика

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

Ссылки на все части

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru