
Привет!
Меня зовут Валентин, я — руководитель направления тестирования моделей машинного обучения в Альфа-Банке. Моя команда занимается тестированием ML-моделей и модельных сервисов для наших клиентов уже более четырех лет, и более трех из них я погружен в наши процессы QA.
Я попал в Альфу через проект компании KTS, над которым работал раньше, и первое время занимался ручным тестированием. За несколько лет прошел путь от линейного тестировщика до руководителя команды из 8 человек, и в этой статье рассказываю о своем опыте и делюсь советами, которые дал бы самому себе несколько лет назад.
Эта статья будет полезна:
тем, кто только начинает выстраивать процессы тестирования моделей;
начинающим тимлидам QA-команд до 10 человек;
тем, кто просто хочет познакомиться с примером организации QA-процесса с нуля.
Я поделюсь этапами развития команды, нашими подходами и инструментами. А ещё расскажу о том, почему принимались те или иные решения, и каких ошибок мы смогли избежать или не смогли.
Свои рассуждения в рамках этой статьи я поделю на две категории:
Проблема — практическая ситуация, для которой можно подобрать типовое решение.
Экзистенциальный вопрос — те задачи, на которые нет «правильных» ответов. Только проверка в реальных практических условиях. В основном это вопросы мотивации, работы с людьми или выбора меньшего из двух зол.
Оглавление:
Уровень 0: когда ты единственный тестировщик
Любой проект начинается с малого — с задач, из которых вырастают процессы и команды. Когда я попал в команду СИМ (система управления моделями), мне поставили задачу: обеспечить функциональное и нагрузочное тестирование ML-моделей перед их деплоем в прод.
До этого мне не приходилось работать с настолько верхнеуровневыми и ответственными задачами, и такая постановка стала для меня вызовом. В то же время я понял, что расширение зоны ответственности может стать моей точкой роста. Но здесь я не буду погружаться в детали своего эмоционального состояния, лучше сразу перейдем к сути.
На тот момент у нас было два типа моделей:
Batch — Python-скрипты, которые запускаются по расписанию в Airflow и работают с определённой выборкой клиентских данных (фичи). Результат работы такой модели — так называемый скор, предсказание склонности клиента к определенным продуктам банка. Сформированный скор скрипты записывали в БД Hadoop.
Online — классические web-сервисы с взаимодействием через REST API, работают в real-time и вызываются сервисами извне. Модель разворачивается на своём URL и принимает на вход JSON определенного формата. В зависимости от данных в запросе модель делает расчёт и возвращает результат также в формате JSON.
Сначала модель проходит обучение (трейн), в ходе которого она работает с тестовыми данными. Если результат трейна удовлетворяет Data Scientist’а, то на основе mlflow-эксперимента собирается образ модели, и она допускается до вычислений на реальных данных (инференса).
В дальнейшем появились и другие типы моделей...
...такие как:
stream-модели, читающие поток сообщений из Kafka;
модели с автопереобучением, способные автоматически запускать трейн без участия человека, основываясь на метриках качества и точности расчетов этой модели;
каскады — наборы последовательно или параллельно запускающихся моделей, в которых результат работы одной модели передается на вход последующей (управляется оркестратором моделей).
Важный факт: на функциональное тестирование любой из этих моделей установлен SLA в 8 рабочих часов.
Итак, в самом начале я был единственным тестировщиком в команде.
Batch-модели проверял вручную, а именно — занимался проверкой лога работы дага инференса в Airflow и корректности записи скора в Hadoop. Online-модели проверял по чеклистам, поскольку на поддержку тест-кейсов просто не хватило бы ресурса (да и поддерживать их, по сути, незачем). Первое время вёл все чек-листы в Confluence, но довольно быстро перешел на Postman-коллекции — это удобнее во всех планах.
Нагрузочное тестирование проводил с помощью Locust. Работал он кривовато и не особо удобно, но более сложными инструментами пользоваться я ещё не умел, так как до этого не сталкивался с нагрузочным тестированием в принципе. В отчет по результатам тестов входила проверка утилизации ресурсов CP и RAM на дашборде в Grafana, а также проверка времени ответа модели при заданном RPS.
Основные критерии, которыми я мог руководствоваться для оценки своей работы — субъективное ощущение, что справляюсь с распределением и приоритезацией задач, и скорость выполнения этих задач.
Проблема: ресурсов категорически не хватало, отлаженный процесс отсутствовал, а поток моделей рос.
С таким подходом я и тестировал модели, а в свободное время начал разрабатывать прототип полноценной методики тестирования, но количество задач постоянно увеличивалось. Тестировать за день более двух моделей одновременно — с функциональным и нагрузочным тестированием — было просто невозможно. Я работал в режиме реверса.
Решение казалось довольно очевидным. Нужно было расширять команду тестирования, чтобы высвободить ресурс на развитие более зрелых процессов.
Уровень 1: команда и её расширение
Согласовав решение расширять команду, я собрал профиль кандидата: нужные технические навыки, опыт, личные качества, мотивация и ценности. Этот профиль служил фильтром на начальных этапах отбора, и помогал оценить соответствие кандидатов требованиям.

О процессе собеседования и найме можно писать отдельную статью, так что не буду на них зацикливаться. Здесь я хочу дать две коротких рекомендации:
Нет принципиальной разницы, когда проводить техническое интервью — до или после беседы о софт-скиллах. Кандидат может одинаково не подойти как по хардам, так и по софтам.
Даже после прохождения HR-фильтра будьте готовы к тому, что от большинства соискателей придется отказаться — лучше вас никто не знает, какой кандидат реально подходит проекту и команде. В моем случае доля подходящих кандидатов составила около 15%.
После найма 4 тестеров (1 нагрузочный и 3 авто) у меня появилось время на развитие процессов QA.
С приходом специалиста по нагрузочному тестированию началась разработка отдельной методики нагрузочного тестирования. Мы перешли от упрощенных решений к систематизированному подходу, позволяющему контролировать качество моделей через гибкую настройку параметров нагрузки (RPS, динамика роста, длительность) и использование разнообразных метрик и отчетов. Это стало фундаментом для отказа от принципа «быстрее и проще» в пользу стратегии «сложнее, но качественнее».
И мы бы сразу провели ещё целую пачку реформ, но помимо тестирования моделей у нас появился параллельный проект (сервис для вывода моделей), который также подъедал ресурсы тестировщиков.
Возник экзистенциальный вопрос: кросс-функциональная команда или специализация по проектам?
Работа над двумя параллельными проектами потребовала стратегического выбора. Я взвесил два подхода: либо разделять команду по проектам (глубокая специализация), либо сделать её кросс-функциональной. Каждый подход имел свои преимущества.
Кросс-функциональность и её плюсы:
взаимозаменяемость членов команды между проектами;
отсутствие единого держателя знаний и компетенций (нет незаменимых);
более широкий стек у каждого сотрудника, более активная прокачка в разные стороны;
более разноплановые задачи, которые не дают заскучать;
более широкий взгляд на продукт в целом.
Специализация на одном проекте и её плюсы:
отсутствие скачков между контекстами, больше стабильности для каждого сотрудника и меньше стресса;
возможность более глубокого освоения определенного стека;
более быстрый онбординг новичков.
Ключевым фактором принятия решения стала взаимозаменяемость, т.к. при ограниченных людских ресурсах и неудачном стечении обстоятельств (например, при сверхвысоком объёме моделей на тест и одновременном отпуске/болезни одного тестера) мы могли остаться без необходимого количества ресурсов в одной из команд. Поэтому был сделан выбор в пользу кросс-функциональности.
С этого момента работа пошла быстрее: любой из тестеров мог подключаться на любой вид задач в любой команде, а я стал заниматься разработкой тестовой методики, мануалов и процесса онбординга.
Уровень 2: рост и развитие
С ростом команды появилась необходимость умело ей управлять. Первым же делом мы ввели еженедельные ретроспективы. Но на одних ретроспективах далеко не уедешь. Было много вопросов, что нужно для развития и с какой стороны к нему подходить.
Я не стал самостоятельно набивать шишки — это долго и дорого — а решил пройти курс повышения квалификации. За несколько месяцев я прошел курс в школе тест-менеджеров от «Лаборатории качества», в ходе изучения которого внедрял лучшие практики, подходы и инструменты в работу своей команды.
Для начала сделал онбординг более зрелым и быстрым. Получение доступов в банкинге — процесс и без того мучительный и небыстрый, а вдобавок и документация по онбордингу у нас была не вполне адаптированной под новичка. Казалось, что легче созвониться и ответить на вопросы по доступам голосом, чем писать все подробно. Но на практике все оказалось иначе.
Я расписал онбординг во всех деталях, добавил скринов и даже видео-гайды для тех, кому легче воспринимать информацию в формате видосика. В итоге все стали тратить меньше времени на созвоны, и процесс не стопорился каждые полчаса. И срок от момента выхода на работу до первых боевых задач сократился с двух недель до одной.
Также мы добавили в нашу работу следующие нововведения:
ввели матрицу скиллов для более качественного подбора сотрудников;
для оперативного решения проблем на начальных стадиях начали регулярно собирать обратную связь, причем не только от членов команды, но и от смежных команд;
определили ключевые метрики для оценки качества тестирования (скорость тестирования, количество дефектов на проде, процент минимального тестового покрытия);
добавили встречи 1-to-1 и практику кросс-ревью тестов;
выявили основную и самая критичную проблему в текущем процессе тестирования — скорость прохождения ФТ;
для решения сложных и повторяющихся проблем стали применять метод «5 почему». На нем хочу остановиться чуть подробнее.
Метод «5 почему» интуитивно понятен, относительно прост и быстро применим на практике. Каждую идею и проблему мы пропускаем через фильтр уточняющих вопросов, раскрывающих их суть, и в итоге получаем список истинных причин, которые детализировать глубже уже невозможно. С этими причинами мы и работаем дальше.
Например, одна из проблем заключалась в возникновении непонятных багов, дебаг которых занимал много времени и затягивал процесс вывода модели в прод. Назовем её проблемой первого уровня. Почему она возникает? Потому что нет глубокого понимания, как работает система. Это уже «подпричина второго уровня».
Дальше тем же способом выявляем «подпричины третьего уровня»:
Нет документации/неочевидно, где она лежит.
Непонятно, кто может проконсультировать/пошарить экспертизу.
Не хватает знания стека технологий.
Это уже понятные проблемы, с которыми можно работать. А именно:
Узнать, где есть документы, и поставить задачи на разработку недостающих инструкций.
Уточнить у коллег их зоны ответственности и компетенции.
Изучить стек технологий самостоятельно или пройти релевантный курс.
В нашем примере докопаться до сути удалось всего за два «почему». Практика показывает, что даже самые запутанные проблемы можно разобрать за пять итераций. Поэтому я смело рекомендую вам внедрять этот подход, если перед вами стоят задачи высокого уровня абстракции.
Вернемся к нашей истории. На этапе развития передо мной встала новая трудность, а точнее, новый экзистенциальный вопрос: как правильно делегировать задачи? Как распределять их таким образом, чтобы команда не выгорала, а развивалась и училась работать самостоятельно?
Казалось бы, проще всего железной рукой раскидать тикеты и ждать их выполнения. Жаль, конечно, но такой подход работает только в армии, и то не всегда. Нельзя ставить задачи наугад. Они должны быть осмысленными, посильными и — в идеале — интересными человеку, который их выполняет.
Руководитель, который недостаточно понимает своих подчиненных и их скиллы, рискует потратить свое же время на разбор возникших вследствие проблем.
Чтобы лучше понимать свою команду, я составил профиль для каждого сотрудника на основе 1-to-1 встреч. В них собрал информацию о мотивации, потребностях в развитии, определил уровень профессиональной зрелости и, соответственно, адаптировал стиль управления: новичкам давал больше поддержки, объяснения и контроля выполнения, а опытным — свободу принятия решений. Это помогло предотвратить лишний микроменеджмент и снизить риски от возможных ошибок менее опытных коллег.
Основные выводы, которые хочу подсветить:
больше делегирования — залог успеха любого руководителя;
необходимое (но не достаточное) условие оптимизации работы в команде — знание слабых и сильных сторон своих сотрудников;
зона ответственности сотрудника не должна опережать его скиллы, т.е. не стоит делегировать задачи тем коллегам, в подготовке которых вы еще сомневаетесь (здесь как с обгоном на трассе: не уверен на 100 % — не обгоняй).


Уровень 3: автоматизация тестов
По мере стабилизации процессов мы подобрались к следующему важному этапу — к автоматизации. При этом мы исходили из четкого принципа: автоматизация должна снижать трудозатраты на разработку и поддержку Postman-коллекций, тем самым ускоряя тестирование.
При разработке процесса автоматизации тестов я исходил из того, что автотесты должны быть самым надежным и достоверным артефактом, отражающим реальную работу модели, и при этом учитывающим технические требования к её работе.
В основу архитектуры наших автотестов лёг код, из которого автоматически генерируется документация в виде чек-листов с помощью AllureTestOps. То есть автотесты уже сами по себе являлись основой для тестовой документации. Так мы отказались от её ручной поддержки: можно просто писать автотесты, а всё остальное сделает автоматическая генерация тестов из кода. Круто!
Наш стек: Python + Allure + Requests + Pytest.
Флоу тестов: тесты вызываются в Jenkins при сборке инференса модели, достаточно отметить соответствующий чекбокс в пайплайне. Лог автотеста можно посмотреть в даге Airflow.
Дальше автоматически генерируется отчёт о результатах работы автотестов в виде Allure Report, и с помощью утилиты Allurectl он отправляется на облачный сервер Allure Testops. Там на основе отчета генерируется тестовая документация к данной модели.
По сути, автотесты проверяют REST API модели через комплекс сценариев, реализованных на pytest с интеграцией Allure для отчетности. Основная работа происходит по следующей схеме:
1. Подготовка данных и запросов.
Каждый тест начинается с формирования тестового payload (с использованием RequestData.payload
), где:
для негативных сценариев специально удаляются обязательные/опциональные поля через
Helper.delete_key()
;данные копируются через
dc()
.
2. Отправка и валидация.
Через кастомный ApiClient.post
отправляются POST-запросы на заданный URL, после чего:
проверяются статус-коды ответа;
для успешных запросов ответ проходит валидацию через
model_income_t0_validator();
все действия логируются через
logger_fixture.
3. Параметризованное тестирование.
Особенность реализации — использование @pytest.nank.parametrize
для:
последовательной проверки всех обязательных полей (
RequestRequirements_REQUIRED_KEYS
);верификации опциональных полей (
RequestRequirements_OPTIONAL_KEYS
).
4. Тесты интегрированы в инфраструктуру через:
кастомные маркеры pytest (
@pytest.nank.*
) для классификации тестов;Allure-аннотации (
@allure.title, @allure.fastup
) для детализированной отчетности;фикстуры (например,
logger_fixture
) для сквозного логирования.
Такой подход обеспечивает проверку как happy path (успешных сценариев), так и edge cases (обработки ошибок), что соответствует принципам тестирования API. Все тесты независимы благодаря изолированной подготовке данных и могут выполняться как по отдельности, так и в составе test suite.
А как выбрать тесты под автоматизацию?
Общий совет на тему выбора тестов под автоматизацию: не покрывайте автотестами активно разрабатываемую функциональность, где предвидится много изменений, потому что в результате все тесты придется постоянно рефакторить. Дешевле во всех планах оставить такую функциональность на ручных тестах.
Функционал для автотестов должен быть более-менее стабильным. Лучше всего автоматизировать те тесты, которые цикл от цикла не находят ошибок, чтобы освободить время под ручное тестирование более забагованной функциональности (тех мест, где чаще происходят изменения в коде).
Неправильный выбор функциональности под автотесты может привести к следующим проблемам:
автоматизация не экономит, а крадет еще больше времени на поддержку тестов в актуальном состоянии;
тестирование становится дороже, при этом его метрики качества остаются прежними или деградируют.
К этому этапу медианное время на разработку коллекции составляло 4 ч, а ревью коллекции — 1 ч. Мы выделили следующие плюсы автоматизации на фоне Postman:
Можно не поддерживать тестовую документацию за счет интеграции с Allure TestOps, это экономит время и дает более удобную и полную отчетность в Allure в сравнении с Postman.
Нет ограничения на количество пользователей, в отличие от бесплатной версии Postman.
Менее ограниченная логика — раньше сложные сценарии (условия, циклы) требовали написания скриптов на JS, что не подходило к нашему стеку.
Гибкость — в Postman было сложно реализовать динамическую параметризацию (например, генерацию данных на лету).
Если тест в Postman падал, разобраться в причине было сложнее, чем в случае с кодом.
Однако мы не отказались от использования Postman полностью. Оставили его для быстрых проверок и ручного тестирования там, где это необходимо.
Также часто для оценки выгоды от автоматизации стали использовать следующую формулу (чем выше ROI, тем выгоднее):
Определяем время на прохождение ручного теста.
Умножаем на количество прохождений тестов в релиз/спринт.
Прибавляем буферное время (сколько закладываем на риски), получаем время, которое будет потрачено на ручное тестирование.
Оцениваем время на разработку автотестов.
К оценке прибавляем время на анализ результатов автотеста, умноженное на количество прохождений автотестов в спринт/релиз.
Прибавляем буферное время, получаем время, которое будет потрачено на автоматическое тестирование.
Сравниваем результаты, полученные в п.3 и п.6. Если п.6 меньше, то автоматизацию считаем выгодной.


После автоматизации тестов для online-моделей мы взялись за тесты для batch-моделей.
Целей было две:
Автоматически проверять запись скоров в БД.
Добавить проверку работы модели на невалидных синтетических данных, чтобы увеличить тестовое покрытие.
Архитектура решения следующая: сначала запускается пайплайн инференса batch-модели, и на этом же этапе происходит выкачка библиотеки с синтетическими тестами из Artifactory. После завершения инференса происходит проверка ключа autotests=true
в конфиге с моделью. Далее проверяется соответствие кода модели стандартному шаблону и запускается код автотеста. Результаты работы выводятся в лог. Если модель не соответствует шаблону или ключ autotests=false
отсутствует, то тесты не запускаются.
Тесты запускаются из тестовой ветки проекта в инференс-пайплайне Jenkins.
После загрузки данных read_data берется несколько строк из датафрейма pandas/pyspark и генерируем синтетические данные для проверки негативных кейсов (пустые строки, NaN, неверный тип данных и т.д.).
На этапах препроцессинга и инференса используем сгенерированные синтетические данные по тестам.
По завершении тестов результаты выводятся в лог пайплайна.
Очищаются тестовые данные.

Виды проверок:
Проверка класса на наличие необходимых методов.
Проверка класса на тип входных аргументов.
Проверка скоров в БД на граничные значения класса эквивалентности.
Проверка скоров в БД на валидный тип данных.
Проверка на невалидных типах данных (неверный тип данных в фичах, пустые строки, Nan, дубли).
Проблема + Экзистенциальный вопрос. Как и зачем?
Необходимость разработки таких тестов сразу встала под вопрос. Зачем нам тратить время на разработку, если разница во времени между ручной и автоматической проверками не так велика?
Тут мы смотрим на перспективу: один раз пишем код для шаблонной модели, и он работает всегда в любое время суток, достаточно datascientist’у заполнить конфиг для его работы. К тому же, у синтетических тестов в проверке работы модели есть преимущество: если в БД запишутся невалидные данные, то модель не упадет, а выдаст ошибку.
Планы на будущее
До конца 2025 года мы планируем следующие улучшения:
Прямо сейчас мы разрабатываем автотесты для AutoML-моделей. Используя наработки для batch-моделей, мы можем покрыть тестами и самообучающиеся модели.
Собираемся расширить покрытие процессов тестовыми метриками. Текущих метрик недостаточно, а для анализа нужно иметь больше данных, чтобы выявить точки роста и дальнейшего улучшения процессов.
Будем развивать индивидуальные планы развития (ИПР) и матрицу грейдов. Это поможет сделать развитие команды более прогнозируемым и даст каждому члену команды понимание, как ему развиваться и какие именно скиллы ему для этого нужны.
Выводы
Наш юнит прошел тернистый путь, и на его основе я хочу сформулировать несколько советов для тех, кто тоже учится выстраивать слаженную работу в подразделении тестировщиков.
Угнаться и за процессами, и за закрытыми тикетами в одиночку очень тяжело. Разумнее будет расширить команду и заняться налаживанием процессов.
Делегирование — ключ к росту. Но только осмысленное и структурированное, с учетом навыков и мотивации членов команды.
Автоматизация требует анализа эффективности (Например ROI). Если автотест «дороже» и «медленнее» ручного — стоит задуматься о его целесообразности.
Автоматизация экономит время, но требует правильного подхода к реализации.
Непрерывное улучшение процессов — залог развития.
Сотрудничество с другими командами помогает решать сложные технические задачи.
Надеюсь, что эта история о тернистом пути нашей команды будет вам полезна. А еще было бы интересно узнать про ваш опыт, так что делитесь в комментариях, сталкивалась ли ваша команда с похожими проблемами и вопросами. И задавайте вопросы, если где-то не хватило конкретики — с радостью расскажу более подробно.