Всё, что вы хотели знать про пирамиду тестирования, но не знали как спросить
Как родилась пирамида тестирования
Пирамида тестирования — это модель, впервые описанная Майком Коном в книге »Succeeding with Agile: Software Development Using Scrum» в 2009 году. Майк является одним из авторов метода разработки программного обеспечения Scrum.
Его пирамида состояла из нескольких уровней тестирования, которые распределены в зависимости от степени детализации, сложности разработки и имплементации тестов, а также количеству тестов, проводимых на каждом уровне. В исходной версии Кона было всего 3 уровня:
модульные (юнит-) тесты;
сервисные тесты;
тесты пользовательского интерфейса (UI).
Пирамида тестирования Майка Кона
Важно отметить, что пирамида тестирования Майка Кона — это пирамида автоматизации тестирования, и предполагается, что на всех трех уровнях тестирования должны писаться автотесты.
Фундаментом пирамиды служат юнит-тесты, так как их проще всего разработать. Тесты пользовательского интерфейса, напротив, сложны в написании и очень легко ломаются при незначительных изменениях какого-либо компонента в интерфейсе, поэтому они находятся на вершине пирамиды. К сервисным тестам Майк относит тестирование сервисов отдельно от пользовательского интерфейса, но при этом он берет во внимание, что существуют архитектуры не только сервис-ориентированные. Для любой архитектуры на этом уровне пирамиды должны находиться тесты, которые проверяют, что делает приложение в ответ на некоторый ввод данных через программный интерфейс.
Почему пирамида Кона включает в себя только автоматизированные тесты и значит ли это, что ручные тесты не нужны? Здесь стоит обратиться к истокам карьеры Майка, и обнаружить, что он начинал свой путь в роли программиста, работающего на C и C++. Уволившись из большой корпорации, где был специальный отдел тестирования, он попал в маленький стартап из 8 человек, среди которых не было ни единого тестера, и за качество продукта приходилось отвечать ему и его коллеге программисту. Нехватка денег у компании на тестеров вынудила разработчиков отставить панику и создать собственный комплект инструментов и методов автоматизированного тестирования приложения. Тестировщиков в конце концов наняли, но к тому моменту абсолютно все программисты того стартапа пришли к пониманию, что за качество продукта должна отвечать команда целиком и делать это непрерывно.
Как бы там ни было, автоматизировать все тесты для всех сред не представляется возможным, именно поэтому в командах всегда нужны будут ручные тестировщики, но тестирование вручную, по словам Кона, стоит рассматривать больше, как способ проведения исследовательского или экспериментального тестирования. В ходе такого тестирования можно не только находить дефекты, но и выявлять пропущенные кейсы, которые затем можно добавить на подходящий уровень автоматизированного тестирования.
Современная пирамида тестирования
Майк старался донести понимание важности интеграции тестирования в разработку до всех компаний-разработчиков программного обеспечения, где ему удалось поработать. В 2024 году уже вряд ли кому-то придется это объяснять, но как правильно реализовать тестирование известно не всем. Помочь в вопросе может не что иное, как созданная Коном абстракция. Она позволит командам определить стратегию тестирования на проекте и выстроить иерархию тестов.
В настоящее время эта модель уже кажется чересчур упрощенной. Эволюция технологий разработки, используемых архитектур, инструментов и ролей в тестировании породила собой совершенно новые уровни. Кроме того, мы живем во времена фреймворков и библиотек для одностраничных приложений, где становится очевидным, что тесты пользовательского интерфейса не обязательно должны находиться на самом высоком уровне пирамиды — их вполне можно реализовать на нескольких уровнях.
Тем не менее, пирамида Кона служит хорошим базисом для разработки собственной модели на проекте. Слои пирамиды можно дополнять и адаптировать под любой контекст и архитектуру, определяя ответственные роли для каждого уровня. Чтобы построить собственную модель, нужно:
идентифицировать проблемные места;
определить необходимые виды тестов;
определить необходимое тестовое покрытие;
определить стоимость каждого из видов тестов с учетом используемых технологий, бизнес-домена, архитектуры и существующего «legacy»;
определить ответственные роли для каждого уровня.
Таким образом, современная пирамида тестирования может выглядеть следующим образом:
Пример современной пирамиды тестирования
При проектировании и реализации пирамиды тестирования важно придерживаться следующих ключевых принципов:
тест должен быть на том же уровне, что и тестируемый объект (например, модульный (юнит-) тест должен быть на модульном уровне, нельзя запускать на API уровне тест, который проверяет минимальную единицу кода);
тесты более высокого уровня не тестируют логику более низкого уровня (например, в E2E тестах не должна проверяется логика обработки граничных значений);
чем выше уровень тестирования, тем сложнее и дороже разработка тестов, и тем меньше количество проводимых тестов;
чем выше уровень тестирования, тем более важными и критичными для бизнеса являются тесты, и тем дольше по ним ожидается обратная связь.
Рассмотрим детальнее каждый из уровней современной пирамиды.
Юнит- (модульное) и компонентное тестирование
Оба вида тестирования часто приравнивают к одному, однако у них есть существенные различия.
Юнит- (модульное) тестирование фокусируется на наименьшей единице кода и проектируется самим фронтенд- или бекенд-разработчиком. Модульные тесты выполняются на уровне функций, методов и классов, и должны следовать принципу »one assertion per test» (одно утверждение на тест). Некоторые зависимости в модуле могут заменяться на заглушки и моки (главное различие между заглушками и моками заключается в том, что в одном случае мы управляем состоянием, а в другом — поведением), обеспечивая таким образом высокую скорость исполнения тестов.
Обычно такие тесты пишутся и запускаются разработчиком после написания кода приложения, тем не менее в гибких методологиях разработки написание модульных тестов может предшествовать написанию кода приложения. К примеру, парадигма разработки на основе тестирования (test-driven development, TDD) подразумевает написание автоматизированных юнит-тестов перед написанием кода приложения. Она основывается на повторении коротких циклов: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода. Так или иначе, хороший разработчик должен отдавать в тестирование только тот код, который он сам проверил, чтобы исключить множество циклов доработки по возврату. Именно поэтому юнит-тесты занимают существенную часть пирамиды тестирования.
Юнит-тесты также можно проводить автоматически, встроив в CI/CD-конвейер. В CI-фазе важно, чтобы сборка проходила максимально быстро, поэтому там чаще всего запускают облегченные типы тестов, такие как юнит-тесты.
А что же такое компонентное тестирование? Оно находится уровнем выше модульного, соответственно для него справедлив один из принципов пирамиды — более высокий уровень тестирования не проверяет логику более низкого уровня, то есть компонентный тест не проверяет минимальную единицу кода. Компонентные тесты существуют на уровне целого компонента приложения, и тестируют логику этого компонента в изоляции от других. Один компонент обычно имеет несколько зависимостей и состоит из нескольких сущностей, поэтому для проведения компонентных тестов также используются моки и заглушки.
Если рассматривать бекенд-часть на микросервисной архитектуре, то компонентом можно назвать один из микросервисов приложения. При тестировании отдельного сервиса для имитации внешних компонентов можно использовать моки, а для имитации базы данных — in-memory базы данных, что, однако, может несколько усложнить тест. В компонентных тестах сервис запускается локально, после чего можно обращаться к его эндпоинту.
Если рассматривать фронтенд-часть, то компонентом можно назвать форму с инпутами и кнопками. В таком случае компонентный тест будет проверять работу всей формы целиком в изоляции от других компонентов. При проведении таких тестов можно запускать рендеринг деревьев отдельных компонентов в упрощенной тестовой среде или же запускать все приложение в реалистичной среде браузера.
Поскольку юнит-тесты являются слишком низкоуровневыми и атомарными, их результаты могут не всегда отражать реальное поведение приложения на практике. Компонентные тесты обеспечивают более целостное представление о приложении, но их разработка несколько сложнее юнит-тестов, поэтому они находятся уровнем выше. Точно также как юнит-тесты, компонентные тесты пишутся и запускаются самим разработчиками приложения.
Интеграционное тестирование
Интеграционное тестирование фокусируется на проверке взаимодействия между интегрируемыми компонентами и обычно проводится разработчиками. Концептуально интеграционные тесты всегда инициируют действие, которое приводит к интеграции с внешней частью. Это означает, что нужно запустить не только собственное приложение, но и интегрируемый компонент.
К примеру, на бекенде тест интеграции со сторонним сервисом через REST API может выглядеть следующим образом:
запуск приложения;
запуск инстанса стороннего сервиса;
запуск функции в коде, которая считывает данные из API стороннего сервиса;
проверка, что приложение правильно обрабатывает ответ.
Тест интеграции с базой данных может выглядеть следующим образом:
запуск базы данных;
подключение приложения к базе данных;
запуск функции в коде, которая записывает данные в базу данных;
проверка, что ожидаемые данные записаны в базу путем их чтения из базы данных.
Интеграционное тестирование подразумевает тестирование каждой точки интеграции по отдельности, и такие тесты необходимы для всех фрагментов кода, где выполняется сериализация и десериализация данных:
вызовы REST API своих или сторонних сервисов;
чтение и запись данных в базе данных;
чтение из очереди и публикация сообщений в нее;
чтение из файловой системы и запись в нее;
чтение из объектного хранилища и запись в него.
При проведении интеграционных тестов можно либо локально запускать внешние зависимости, либо интегрироваться по сети с выделенным тестовым инстансом стороннего сервиса. Интеграция с сервисом по сети — это типичное свойство широкого интеграционного теста, но такие тесты обычно труднее писать, и они медленнее работают.
На фронтенде интеграционные тесты — это тесты, которые позволяют проверять взаимодействие между компонентами приложения и отправку запросов на бекенд. В зависимости от инструментов тесты могут изолироваться от реального запуска браузера, что делает их такими же быстрыми и стабильными, как юнит-тесты.
Контрактное тестирование
Как специфический подвид интеграционного тестирования выделяют также контрактное тестирование. Оно фокусируется на проверке того, что два сервиса совместимы друг с другом. В таком тестировании принимают участие две стороны — потребитель, который использует API и поставщик, который его предоставляет.
Интерфейс (или контракт) всегда должен быть четко прописан, так как зачастую бывает, что сервисы потребителей и поставщиков распределены между разными командами.
Результатом прогона контрактных тестов является констатация того, что поставщик API уверен в исправной работе потребителя.
Тестирование API
Тестирование API фокусируется на проверке программного интерфейса приложения с целью убедиться, что он соответствует ожидаемой функциональности, безопасности и производительности. Данный вид тестирования является более высокоуровневым, так как тестирование программного интерфейса — это тестирование черного ящика, и за это несет ответственность уже тестировщик, которому не обязательно детально разбираться в коде приложения.
Для проведения подобного вида тестирования необходимо развернуть инстанс тестируемого сервиса, а также, при необходимости, инстансы сервисов, с которыми интегрирован тестируемый сервис.
API тест для проверки функциональности выполняется путем отправки запроса к эндпоинту и сравнения ответа с ожидаемым результатом. Функциональные тесты на API считаются критически важными для автоматизации — они должны быть главным приоритетом у тестировщика-автоматизатора в команде. Начинать этот вид тестирования лучше на ранних этапах цикла разработки, то есть прежде, чем будет готов пользовательский интерфейс. Это позволит устранить большинство существующих ошибок до того, как они превратятся в более серьезные проблемы.
Тестирование производительности API включает в себя измерение время отклика, измерение стабильной пропускной способности при приемлемой нагрузке и избежание проблем с задержкой.
Тестирование безопасности API включает в себя проверку аутентификации и авторизации, тестирование на наличие известных уязвимостей и защиту от атак путем внедрения или утечек данных.
Для проведения нагрузочного тестирования и тестирования безопасности существуют отдельные роли в тестировании — нагрузочный тестировщик и тестировщик безопасности, которые не всегда входят непосредственно в команду разработки приложения, поэтому эти виды тестов не включаются в пирамиду тестирования.
E2E тестирование
E2E (end-to-end) тестирование фокусируется на проверке поведения приложения при полном прохождении определенного пользовательского сценария и проводится тестировщиками. Данный вид тестирования затрагивает все подсистемы, компоненты и их интеграции, которые участвуют в цепочке бизнес-процессов приложения, и может гарантировать, что приложение работает должным образом при реальных жизненных сценариях. Обычно такое тестирование проводится после завершения интеграционного тестирования отдельных компонентов.
В E2E тестах не используются моки или заглушки, так как на этом уровне тестирования важно убедиться, что системная интеграция работает так, как ожидается.
Поскольку Е2Е тесты затрагивают всю цепочку действий пользователя и занимают довольно много времени, их количество минимально. E2E тесты не нацелены на абсолютное покрытие и не пытаются глубоко тестировать функциональность, на этом уровне пирамиды тестируются только критически важные бизнес-сценарии.
Проводить E2E тестирование можно через пользовательский интерфейс — это самое полное тестирование, какое только можно провести. Но если у приложения нет графического интерфейса или в проекте нет необходимости проводить такие тесты через UI, то они проводятся только для API.
Тестирование пользовательского интерфейса
Тестирование пользовательского интерфейса находится на самом высоком уровне пирамиды и фокусируется на проверке корректности работы и удобства использования пользовательского интерфейса приложения. Объектами тестирования на этом уровне выступают:
внешний вид и функциональность компонентов интерфейса;
отображение подсказок и сообщений об ошибках для пользователя;
навигация;
отзывчивость.
Тестирование пользовательского интерфейса обычно проводится ручными тестировщиками, но также существуют и способы, позволяющие автоматизировать такие тесты при помощи скриптов и инструментов для имитации взаимодействия пользователя с приложением.
Ручное тестирование пользовательского интерфейса предполагает, что тестировщики взаимодействуют с пользовательским интерфейсом программного обеспечения так же, как и конечный пользователь — нажимают на кнопки и вводят данные. Таким образом исследуется удобство использования приложения и находятся визуальные проблемы, которые не всегда можно обнаружить с помощью автоматического тестирования.
Заключение
О классификации тестов говорить очень сложно, так как в сообществе разработчиков до сих пор не сформировались четко определенные термины. Особенно часто возникают проблемы с пониманием интеграционных и компонентных тестов.
Важно понимать, что не стоит зацикливаться на конкретных определениях, а вместо этого нужно определиться с теми видами тестов и их охватом, которые действительно необходимы вашей команде и согласовать термины, которых бы придерживались все участники.