Автоматизированное тестирование для CI/CD

Задача непрерывной интеграции и развертывания (CI/CD) — позволить командам разработчиков часто выпускать пользователям работающее ПО, таким образом принося им пользу и узнавая, как продукт используется в реальном мире. Чтобы не отставать от конкурентов, многие компании начали использовать практику DevOps.

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

Зачем автоматизировать CI/CD-тестирование?

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

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

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

Непрерывная интеграция предполагает регулярные коммиты изменений в основную ветку (master), которые запускают сборку и обеспечивают высокое качество приложения. Чтобы получить максимум пользы от CI, члены команды должны делать коммиты хотя бы раз в день. Предположим, у вас не так-то много разработчиков. Чтобы управлять запуском автотестов вручную, вам понадобится внушительное количество QA-инженеров, которые будут вынуждены постоянно выполнять одну и ту же работу. Именно поэтому появляется автоматизированное тестирование.

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

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

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

Место тестирования в CI/CD-процессе

Тестирование выполняется на нескольких стадиях пайплайна.

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

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

Многие инструменты автоматизированного тестирования поддерживают интеграцию с инструментами CI/CD — вы можете передавать данные для тестов прямо в пайплайн и выполнять тесты поэтапно, получая результаты после каждого шага. Некоторые CI-инструменты также позволяют переводить сборку на другой этап в зависимости от результата тестов на предыдущем этапе.

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

К приоритизации создания и запуска автоматизированных тестов удобно подходить с позиции тестовой пирамиды.

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

Пирамида тестирования помогает составить представление о том, как приоритизировать тесты в CI/CD-пайплайне, определить нужное количество тестов и порядок их выполнения.

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

Название «пирамида тестирования» — не слишком точное, но исходная идея ясна: начать стоит с создания прочной базы автоматизированных юнит-тестов, которые быстро и легко выполнить, после чего можно перейти к более сложным для написания и долгим при выполнении тестам, а закончить — небольшим количеством наиболее сложных проверок. Какие типы CI/CD-тестов стоит включить? Давайте рассмотрим варианты.

Юнит-тесты

Юнит-тесты по праву занимают место в основании пирамиды. Эти тесты призваны проверить, что ваш код работает ожидаемым образом, адресуя как можно более мелкие составляющие поведения кода. Юнит-тесты, как правило, пишут разработчики — параллельно с написанием самого кода. Это естественным образом вытекает из методологии разработки через тестирование (Test Driven Development, TDD). Однако чтобы писать юнит-тесты, вовсе не обязательно следовать TDD.

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

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

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

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

Сквозные тесты

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

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

Тестирование производительности

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

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

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

Тестовые окружения

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

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

Обработка обратной связи

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

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

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

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

Означает ли CI/CD и автоматизация тестирования конец эпохи ручного тестирования?

Среди тех, кто только начал работать с CI/CD, распространено заблуждение о том, что автоматизированное тестирование устраняет необходимость в ручном тестировании и в профессиональных тестировщиках.

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

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

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

Непрерывное улучшение авто-тестов

Автоматизированное тестирование занимает центральное место в любом CI/CD-пайплайне.

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

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

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

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