Привет, Хабр! Меня зовут Ирина Иванова, я работаю QA-инженером в команде разработки внутренних проектов в Учи.ру. Мы занимаемся такими сервисами, как виджет поддержки, личный кабинет сотрудников пользовательской поддержки, карьерный сайт и так далее.
Все проекты разные, у них разные задачи. И трудности, с которыми приходилось сталкиваться во время работы над ними, тоже были разными. Но одной из повторяющихся проблем стало застревание фич на этапе контроля качества, неравномерная нагрузка на инженера по тестированию и, как следствие, замедление скорости релизов. Ниже я расскажу, какое мы нашли решение.
Кто виноват и что делать
Вы когда-нибудь сталкивались с тем, что при тестировании не работает вообще ничего? Возможно, потому, что фронтенд-разработчик ожидает от API один формат данных, а бэкенд-разработчик реализовал другой. Или формат данных на бэке меняется в процессе разработки (только никто об этом не знает).
Да, реальный процесс разработки часто отличается от теории. Ведь предполагается, что сначала происходит постановка задачи, затем параллельная разработка бэкенда и фронтенда — и все передается на тестирование и релиз.
Но на практике обычно иначе: после постановки задачи разрабатывается бэкенд, потом (на его основе) — фронтенд, а после все передается инженерам по тестированию. В любом случае, контроль качества происходит уже после полной реализации фичи.
Представим, что при разработке бэкенда возникло какое-то количество багов, но их вовремя не нашли и не поправили. При разработке фронтенда число багов удвоилось, и в таком виде задача попала на тестирование. В идеальном мире у инженера по тестированию есть в наличии вся документация и четко сформулированные задачи. Но так бывает далеко не всегда.
Получается, что с каждым переходом у задачи количество багов увеличивается, но при этом теряется контекст. Когда задача прилетает на тестирование, контекст уже минимальный, а число багов зашкаливает. Контроль качества оказывается узким горлышком, на котором все тормозится. Что делать — фиксить все баги? А если дедлайн уже горит — катить, как есть?
Чтобы жить было легче, процесс можно выстроить по-другому: начинать тестирование одновременно с разработкой бэкенда и фронтенда. Это обеспечивает раннее выявление багов и удешевляет их устранение. Такой подход называется Shift Left.
Расскажу, как он применяется у нас в компании. Команда вырабатывает решение, затем фиксирует его в доступной и понятной всем форме. После этого начинается разработка на основе зафиксированных артефактов, на этом этапе можно описывать тест-кейсы и писать автотесты.
Артефакты нужны, чтобы обозначить договоренности между всеми участниками команды разработки.
Семь бед — один ответ
Но что, если в процессе разработки действительно стало понятно, что нужно изменить API? Как мы уже выяснили, делать это в одностороннем порядке — плохая идея.
Конечно, можно собрать встречу и договориться заново. Но это не всегда необходимо, а если речь идет о кросскомандной разработке, то это еще и дорого.
Тут нам помогают контракты. Они позволяют предложить изменения так, чтобы все участники узнали об этом, могли оставить комментарии, заблокировать или одобрить изменения. Такой подход называется Contract First.
Контракт принадлежит не конкретному человеку, а всем, заключившим его. Чтобы опубликовать новую версию контракта, необходимо согласие всех владельцев.
Контракт содержит в себе некую договоренность: например, декларативное описание API. Мы используем самый популярный формат — OpenAPI.
С помощью OpenAPI можно генерировать:
интерфейс для провайдера API;
клиент для потребителя API;
тесты и документацию;
Mock-данные.
Если API уже есть, а контракта нет, можно сгенерировать OpenAPI-схему из API. Сгенерированная схема дополняется, добавляется в контракт и далее используется по стандартному сценарию.
Подход Contract First помогает обеспечению качества на всех этапах:
на этапе проектирования — проверить API на соответствие требованиям;
на этапе разработки — использовать примеры данных из OpenAPI для тест-кейсов , писать тесты для API , проводить автотесты для фронтенда на Mock’ах;
на этапе тестирования фичи — автоматически запускать изолированные тесты на Mock’е;
на этапе регрессов — для тестирования API (при необходимости).
Как видите, такой подход, позволяет выявлять дефекты на каждом этапе разработки проекта.
Для небольших изолированных проектов и команд (например, для разработки MVP) может использоваться подход Code First. В этом случае схема API, как правило, автоматически генерируется из кода бэкенда. Результат нельзя назвать контрактом, поскольку он односторонне «навязан» остальным участникам процесса, и владеет им только бэкенд-разработчик.
Как мы работаем с контрактами
Мы в Учи.ру стремимся развивать удобную инфраструктуру для разработки, поэтому создали для команд сервис, в котором хранятся версии всех контрактов. Мы назвали его API Explorer. Еще в нем можно протестировать запросы, выбрав API-сервер, к которому мы обращаемся (прод, стэйдж), а также найти контракт в Git.
Если надо быстро отредактировать маленький кусочек в контракте, то можно внести необходимые изменения, закоммитить и создать Pull Request прямо через веб-интерфейс Git. Большие изменения удобнее вносить с помощью IDE.
Для удобного ручного тестирования OpenAPI схему из контракта можно импортировать в Postman (или в любой другой инструмент — например, SoapUI). Это позволяет не вводить пути и параметры запросов вручную, а также делает проще проверку валидности возвращаемых данных.
Однажды мы запускали проект, который нужно было достаточно быстро отправить на прод, но разработчики все равно написали контракт. В процессе разработки мы узнали, что на реализацию API не хватает ресурсов бэкенд-разработки.
Благодаря контракту проект уехал на прод на Mock’е: он позволил сымитировать программное окружение, потому как мы знали, какие данные придут и какие данные нужно отдать. А если бы пришлось реализовывать API, то контракт бы ускорил и упростил как разработку, так и интеграцию с фронтенд-приложением.
Контракты и автотесты
Недавно у нас в команде появилось автоматизированное интеграционное тестирование. Вот как оно работает: запускается веб-приложение, источником данных для которого является Mock-сервер, поднятый из контрактов. Мы не эмулировали сложную логику — это дорого. Поэтому тестируем на Mock’е только простые сценарии. На схеме ниже показано, как реализовано тестирование для бэкенд- и фронтенд-приложения.
Раньше весь наш код автотестов лежал в отдельном от кода приложения репозитории. Это приводило к рассинхронизации тестов и функционала. Например, тесты в master-ветке написаны под тестируемый релиз, а нам нужно срочно выкатить хотфикс без новых изменений. В ручном режиме можно переключиться на старый коммит и запустить регрессы. Но при запуске в CI невозможно понять, куда следует переключаться. Либо пришлось бы в репозитории тестов заводить аналогичные ветки репозиторию приложения, что неудобно и легко может сломаться. Поэтому, несмотря на то, что тесты написаны на другом языке, мы положили их в один репозиторий с кодом приложения — и теперь тесты всегда соответствуют коду приложения.
Выводы
Контракты — это не «волшебная таблетка», у них есть свои минусы. К тому же, не всем может быть очевидно, как описать API приложения на самом раннем этапе. Придется затратить дополнительное время, чтобы сначала продумать и написать контракт.
Однако контракты позволяют реже переделывать уже готовое решение и устранять баги с меньшими затратами, ведь тестирование включается на самом раннем этапе. Также разработка на основе контрактов помогает формировать документацию по проекту и всегда поддерживать ее в актуальном состоянии, так как код генерируется из контракта, а не наоборот.
Сейчас в нашу команду мы ищем трех инженеров по тестированию. Если хочешь развивать школьный EdTech вместе с нами, присоединяйся к нашей команде.