Всё, что вы хотели знать про пирамиду тестирования, но не знали как спросить

Как родилась пирамида тестирования

Пирамида тестирования — это модель, впервые описанная Майком Коном в книге »Succeeding with Agile: Software Development Using Scrum» в 2009 году. Майк является одним из авторов метода разработки программного обеспечения Scrum.

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

  • модульные (юнит-) тесты;

  • сервисные тесты;

  • тесты пользовательского интерфейса (UI).

Пирамида тестирования Майка Кона

Пирамида тестирования Майка Кона

Важно отметить, что пирамида тестирования Майка Кона — это пирамида автоматизации тестирования, и предполагается, что на всех трех уровнях тестирования должны писаться автотесты.

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

Почему пирамида Кона включает в себя только автоматизированные тесты и значит ли это, что ручные тесты не нужны? Здесь стоит обратиться к истокам карьеры Майка, и обнаружить, что он начинал свой путь в роли программиста, работающего на C и C++. Уволившись из большой корпорации, где был специальный отдел тестирования, он попал в маленький стартап из 8 человек, среди которых не было ни единого тестера, и за качество продукта приходилось отвечать ему и его коллеге программисту. Нехватка денег у компании на тестеров вынудила разработчиков отставить панику и создать собственный комплект инструментов и методов автоматизированного тестирования приложения. Тестировщиков в конце концов наняли, но к тому моменту абсолютно все программисты того стартапа пришли к пониманию, что за качество продукта должна отвечать команда целиком и делать это непрерывно.

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

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

Майк старался донести понимание важности интеграции тестирования в разработку до всех компаний-разработчиков программного обеспечения, где ему удалось поработать. В 2024 году уже вряд ли кому-то придется это объяснять, но как правильно реализовать тестирование известно не всем. Помочь в вопросе может не что иное, как созданная Коном абстракция. Она позволит командам определить стратегию тестирования на проекте и выстроить иерархию тестов.

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

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

  1. идентифицировать проблемные места;

  2. определить необходимые виды тестов;

  3. определить необходимое тестовое покрытие;

  4. определить стоимость каждого из видов тестов с учетом используемых технологий, бизнес-домена, архитектуры и существующего «legacy»;

  5. определить ответственные роли для каждого уровня.

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

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

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

При проектировании и реализации пирамиды тестирования важно придерживаться следующих ключевых принципов:

  1. тест должен быть на том же уровне, что и тестируемый объект (например, модульный (юнит-) тест должен быть на модульном уровне, нельзя запускать на API уровне тест, который проверяет минимальную единицу кода);

  2. тесты более высокого уровня не тестируют логику более низкого уровня (например, в E2E тестах не должна проверяется логика обработки граничных значений);

  3. чем выше уровень тестирования, тем сложнее и дороже разработка тестов, и тем меньше количество проводимых тестов;

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

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

Юнит- (модульное) и компонентное тестирование

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

Юнит- (модульное) тестирование фокусируется на наименьшей единице кода и проектируется самим фронтенд- или бекенд-разработчиком. Модульные тесты выполняются на уровне функций, методов и классов, и должны следовать принципу »one assertion per test» (одно утверждение на тест). Некоторые зависимости в модуле могут заменяться на заглушки и моки (главное различие между заглушками и моками заключается в том, что в одном случае мы управляем состоянием, а в другом — поведением), обеспечивая таким образом высокую скорость исполнения тестов.

Обычно такие тесты пишутся и запускаются разработчиком после написания кода приложения, тем не менее в гибких методологиях разработки написание модульных тестов может предшествовать написанию кода приложения. К примеру, парадигма разработки на основе тестирования (test-driven development, TDD) подразумевает написание автоматизированных юнит-тестов перед написанием кода приложения. Она основывается на повторении коротких циклов: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода. Так или иначе, хороший разработчик должен отдавать в тестирование только тот код, который он сам проверил, чтобы исключить множество циклов доработки по возврату. Именно поэтому юнит-тесты занимают существенную часть пирамиды тестирования.

Юнит-тесты также можно проводить автоматически, встроив в CI/CD-конвейер. В CI-фазе важно, чтобы сборка проходила максимально быстро, поэтому там чаще всего запускают облегченные типы тестов, такие как юнит-тесты.

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

Если рассматривать бекенд-часть на микросервисной архитектуре, то компонентом можно назвать один из микросервисов приложения. При тестировании отдельного сервиса для имитации внешних компонентов можно использовать моки, а для имитации базы данных — in-memory базы данных, что, однако, может несколько усложнить тест. В компонентных тестах сервис запускается локально, после чего можно обращаться к его эндпоинту.

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

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

Интеграционное тестирование

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

К примеру, на бекенде тест интеграции со сторонним сервисом через REST API может выглядеть следующим образом:

  1. запуск приложения;

  2. запуск инстанса стороннего сервиса;

  3. запуск функции в коде, которая считывает данные из API стороннего сервиса;

  4. проверка, что приложение правильно обрабатывает ответ.

Тест интеграции с базой данных может выглядеть следующим образом:

  1. запуск базы данных;

  2. подключение приложения к базе данных;

  3. запуск функции в коде, которая записывает данные в базу данных;

  4. проверка, что ожидаемые данные записаны в базу путем их чтения из базы данных.

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

  • вызовы REST API своих или сторонних сервисов;

  • чтение и запись данных в базе данных;

  • чтение из очереди и публикация сообщений в нее;

  • чтение из файловой системы и запись в нее;

  • чтение из объектного хранилища и запись в него.

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

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

Контрактное тестирование

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

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

Результатом прогона контрактных тестов является констатация того, что поставщик API уверен в исправной работе потребителя.

Тестирование API

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

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

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

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

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

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

E2E тестирование

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

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

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

Проводить E2E тестирование можно через пользовательский интерфейс — это самое полное тестирование, какое только можно провести. Но если у приложения нет графического интерфейса или в проекте нет необходимости проводить такие тесты через UI, то они проводятся только для API.

Тестирование пользовательского интерфейса

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

  • внешний вид и функциональность компонентов интерфейса;

  • отображение подсказок и сообщений об ошибках для пользователя;

  • навигация;

  • отзывчивость.

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

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

Заключение

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

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

© Habrahabr.ru