Как починить QA-отдел, или Ещё один переезд в Go

d52ab001fcc4b247f23d61c492717db5.png

Привет, Хабр.  

Меня зовут Саша, я занимаюсь разработкой инструментов мониторинга тестирования, но по факту ещё и исполняю роль руководителя направления QA в домене Маркетплейса, ответственном за создание всех карточек товаров на Ozon.  

Если вкратце, наша задача заключается в сопровождении селлера от этапа загрузки файла Excel, в котором есть сто тысяч пар носков, до момента, когда все эти носки окажутся на витрине Ozon. Очевидно, что мы занимаемся не только парсингом экселек — у нас есть и highload-сервисы раздачи контента, несколько стейт-машин, системы с мастер-данными всего Озона, и немалый отдел ML, так что мы регулярно сталкиваемся со сложными и нестандартными задачами, подход к решению которых, впрочем, мы всё-таки пытаемся стандартизировать. 

За последние полтора года, как наше направление отделилось от основной разработки Маркетплейса, QA-отдел вырос в пять раз, с 5 до 25 QA-инженеров. О том, как мы решали вопросы масштабирования команды и плакали пытались справиться с legacy-наследием в доставшихся нам процессах, подробнее расскажу под катом. 

Исходные данные

Мы живём в микросервисной архитектуре, с общим процессом CI/CD, который обязателен к соблюдению. 

070a3d96bd8ac87a7b786753d151fe08.png

Ответственность команды QA — это тесты, запускающиеся после раскатки на стейджинг. Условно мы выделяли два вида тестов:  

  • smoke-тесты — вызов каждого endpoint сервиса, чтобы быстро убедиться, что сервис жив и что-то отвечает на вызовы. 

  • functional-тесты — по факту, всё остальное: от интеграционных взаимодействий до e2e-сценариев. 

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

  • весь цикл работы с Allure: от генерации отчёта до отправки свёрстанного сообщения в специальный slack-канал для тестов;  

  • проверки готовности сервиса, актуальности схем и пр.;  

  • да и вообще весь CI/CD для конкретного проекта с тестами состоял из трёх строк подключения репозитория с общим QA-пайплайном (о чём я рассказывал на последнем TestDriven Conf). 

И всё было прекрасно, пока меня не попросили отвечать за всё это дело.

Большая сила — большая ответственность

С какими проблемами мне сразу довелось столкнуться:  

  • Тесты для сервиса, даже если были написаны, в 100% случаев лежали.  

  • Разработчики привыкли, что тесты лежат, и могли катиться без анализа результатов прогонов автотестов. 

  • Как ни странно, когда количество сервисов растёт, а тестировщиков не прибавляется, релиз могли выкатить не только без тестов, но и без тестировщиков. 

  • При этом разработчики, в принципе, готовы были заниматься тестами, но тесты наши были на Python, а вся разработка — на Go. Культура unit-тестов к нам, конечно, пришла, но не сразу, всё когда-то имеет своё начало :) 

  • Отчёты тестов в Slack — это красиво, но совершенно не функционально, когда всё лежит. 

Disclaimer

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

Часть про найм

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

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

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

Надеюсь избежать тут дискуссии, но в результате нам удалось:  

  1. Сбалансировать грейды. В общем случае нет такого, что джун знает лучше сеньора условный SQL.

  2. Очень прозрачно оценить перспективы роста (ты видишь, за что парень из соседней команды получил повышение).

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

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

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

Часть про автотесты

a519940fa45c35d786f0f12c9d3fbfe0.png

Python прекрасен. А его главная прелесть в том, что даже если вы плохо пишете код, на Python он у вас, скорее всего, заработает — однако горе тем, кому достанется его поддержка. Мой вопрос к командам был очень прост: «А чё ваши прекрасные тесты лежат?» 

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

@retry(AssertionError, retry_count=60, delay=1)

Ничего особенного, вот вы запускаете тесты, например, в восемь потоков. Но если сервис слегка задумался под нагрузкой, то вы до кучи даёте на него дополнительно еще до 8 RPS в течение минуты, не считая фонового трафика. В результате такой прогон просто укладывал сервис, что влияло, кстати, ещё и на тесты смежных сервисов.    

Один раз я нашёл тесты, которые запускались в 96 потоков.

66a5e25dea5ecb967f031029a79356e9.png

Не делайте так.

Ах да, со стороны смежных сервисов слышно обычно: «Да это опять , он вечно лежит». Стоит ли говорить, что у ребят свои такие же тесты… Змеиный клубок во всей его красе. 

Попутно мы разочаровались в методологии «дёрни десять ручек сервиса из десяти, и будь спокоен». Посмотрев на наши инциденты и влияние автотестов на качество, мы поняли, что нам такое тестирование практически ничего не даёт. Единственные кейсы, которые такие тесты ловили, — это проблемы с миграциями, которые разработчики забывали раскатить на стейджинг, но тогда, впрочем, падало вообще всё. Самый грустный случай был, когда у одного из сервисов было на порядок больше тестов, чем у остальных, но при этом всплыла проблема, когда он вывалился из нашего service discovery: трафик на него не шёл, но все тесты были зелёные. Понятно, что здесь грубо пренебрегли пирамидой тестирования, но теорию обычно все хорошо знают, пока не наступят на какие-нибудь грабли :) Дописав пачку интеграционных тестов, мы закрыли вопрос.   

Отдельный момент — тесты с походами в БД. У нас повсеместно используется Postgres-as-a-service, что накладывает на команды некоторые ограничения:  

  1. Напрямую в хост БД ходить нельзя (много всяких причин — от забивания пула коннектов до ротации экземпляров PG).

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

  3. ИБ стали контролировать хранение учётных данных в репозиториях (всё уехало в Vault). Не то, чтобы это заблокировало возможность ходить в базу в тестах, но в целом процесс стал менее тривиальным (что увеличило техническую экспертизу QA-инженеров в том числе: D). 

Но при этом проблемы остались всё те же самые, базу можно было так же легко убить DDoS’ом, как и сервис. 

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

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

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

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

И эти в Go

f64781e336b6bef61550afe2fca2a317.png

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

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

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

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

  • В запуск тестов вшили запрос к сервису подготовки тестовых данных. Сервис передеплоили/нагрузили — и на пару минут все запуски тестов у всех встали. 

  • Захотелось получать метрики с тестов, чтобы как-то их контролировать. 

В какой-то момент появилась необходимость перевести тесты на GRPC-протокол. Фреймворк, конечно, поддержал генерацию PB-клиентов и всего необходимого инструментария, но в этот момент мы осознали, что наша QA-Python-инфраструктура — явный оверхэд для тестирования из-за возросшей сложности и, как следствие, нестабильности. Ну и, конечно, свою роль сыграло то, что разработка (массово пишущая на Go) не очень понимала, чем тестировщики занимаются. Разработчики в массе своей Python не знают, к тому же репозитории с тестами живут отдельно и никто из разработки не горит особым желанием туда лезть.  

Go дал нам ряд очевидных, но грандиозных преимуществ:  

  • Интеграция с разработкой: теперь разработчик не только видит, что покрыто тестами, но и может по башке надавать сам что-то поправить или добавить. Обратное тоже верно. (Более того, мы теперь в паре проектов пробуем внедрить ATDD — когда QA и разработчик одновременно пишут код по одной задаче.)

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

  • Как побочный эффект (что, кстати, оказалось у нас востребованным) — обратное снова верно, QA тоже стали подхватывать мелкие задачки из техдолга, до которых у команды разработки могли не доходить руки долгое время.

  • Мы ушли от кодогенерации в части GRPC, теперь можно просто переиспользовать сервисную инфраструктуру, а это — минус звено в цепочке отказов. 

  • Маркетинг, но — QA-инженеры получают экспертизу в языке разработки, а значит, больше знают о своём сервисе. Как следствие, меньше вопросов к разработчикам (что там куда ходит), возможность помогать в саппорте/дежурствах, да и какие-то проблемы QA может увидеть уже на code review. 

  • Как внезапно для меня оказалось, QA-инженеры, пишущие на Python, почему-то видят для себя в Go развитие и некую мотивацию. Да, один уже ушёл в разработчики своего же сервиса : D 

Остался лишь небольшой вопрос:, а чем заменить фреймворк? Нам нужно было придумать, как решить вопросы интеграции с Allure, реализовать параллельный запуск тестов, собрать альтернативный пайплайн, Slack-отчёты, и ещё бы красивые метрики к этому прикрутить (раз уж мы здесь оказались).  

Но, к счастью, у нас оказался Антон [@koodeex], который за месяц выучил Go, быстренько придумал порт под Allure и с помощью коллег собрал небольшой пакет с Kafka и Vault (и даже попыткой в параметризацию), и теперь успешно рассказывает об этом на конференциях и Хабре в том числе.  

Если вы пишете на Go и затащите его библиотеку к себе в проект, он обрадуется. 

Да, свой сервис под красивые графики у нас тоже появился, всё это тоже благодаря Антону.

3da87d222cf63670eb753fd643d9bb8f.png

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

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

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

  • QA в своем репозитории не ограничен рамками релизного пайплайна. 

  • Можно катнуть тесты в код-фриз, например. Мы один раз словили инцидент, потому что не успели обновить тесты, которые были в мастере на момент заморозки. 

  • Мало кто из QA сразу шарит в Go, поэтому неплохо иметь возможность ошибаться в своём репозитории. 

  • Очевидно, приоритеты у продуктовых задач для команды разработки будут выше, чем у code review тестов, поэтому некоторые МРы долго могут висеть без внимания (есть одна команда, которая отчаялась ждать ревью, и была вынуждена держать тесты отдельно от сервиса).

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

Очевидно, бежать и переписывать всё подряд нецелесообразно, особенно для сервисов, в которых: а) большой объём тестов б) ожидается распил и последующий отказ от их использования, поэтому там мы сосредоточились на поддержке текущего покрытия. Однако для новых сервисов, которых у нас достаточно много, Go — приоритетный выбор, и в какой-то момент мы придём к полной замене.

Вместо завершения

По идее, статья должна была завершиться хэппи-эндом «мы все переехали на Go и стало прекрасно», но нет. Мы внимательно посмотрели в наши тесты, победили какие-то детские болячки, однако дальше нас ждали гораздо более масштабные проблемы, связанные с окружением. В Ozon для тестирования используют общий стейджинг, и на следующем этапе развития нам предстояло решить вопросы его стабильности. Однако ясно, что это — проблема не одного департамента, поэтому для её решения внедрялись механизмы уже на уровне всей компании, что помогло и нам. Но это уже материал для отдельной статьи. 

А еще рекомендую почитать моих коллег, которые решали схожие задачи:

Go, Allure и HTTP, или Как мило тестировать HTTP-сервисы на Go

Привет! Меня зовут Сергей, я старший разработчик в Ozon и раньше вообще не был замечен в QA. Все мы …

habr.com

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

Всем привет! Меня зовут Сергей, и сегодня я расскажу о том, как я искал носки мы выстраивали процесс…

habr.com

Go, я создал: интегрируем Allure в Go красиво

Привет! Меня зовут Антон, я ведущий инженер по тестированию в Ozon: занимаюсь созданием и поддержкой…

habr.com

Go, я создал: пишем тесты на Allure-Go

Привет, Хабр! Вы можете помнить меня по предыдущей статье про Allure-Go, в которой мы коснулись сам…

habr.com

© Habrahabr.ru