Автотесты на проектах нужны. Но, как говорится, автоматизация на вкус и цвет может быть разная. Мы пришли на проект, где уже были автотесты, и смогли улучшить покрытие и ускорить прохождение тестов без фундаментальной революции. В этой статье о том, как нам это удалось.
Пара слов о проекте
Хотя детали проекта мы не можем раскрыть из-за NDA, в общих чертах задача выглядела следующим образом. Мы подключились к разработке fintech API-сервиса, который взаимодействовал с базой данных, возвращая необходимые финансовые объекты (цены, тарифы и прочее). Нашей задачей было тестирование мобильных клиентов для этого сервиса — как web, так и нативных мобильных приложений.
Автоматизация тестирования на этом проекте развивалась постепенно, вместе с усложнением сервиса. Вероятно, так в свое время появились длинные end-to-end-тесты, которые мы и застали на проекте. Большая часть из них к тому моменту уже не работала, поскольку сервис изменился, а тесты поддерживать было некому — единственный автоматизатор покинул проект задолго до нашего появления.
Даже те тесты, которые вроде бы соответствовали функционалу, порой падали из-за путанницы с версиями или недоступности внешних ресурсов. Для тестирования использовалась отдельная инфраструктура — тестовые стенды, где для экспериментов разворачивались необходимые версии. Доступ к ней имели разные рабочие группы, и они не всегда действовали согласованно. В результате действий одной группы мог отвалиться какой-нибудь важный API, используемый нашим сервисом, из-за которого даже работающий тест переставал проходить. Т.е. тест уже не показывал работоспособность самого сервиса, а относился скорее к тестовой инфраструктуре в целом.
Как мы подошли к хаосу
Казалось бы, в этой ситуации надо бросать все старые наработки и строить тестирование заново. Но мы поступили более “гуманно”. Саму структуру тестирования сохранили, сосредоточившись на решении конкретных проблем — медленном проходе тестов, их нестабильности и недостаточном покрытии тест-кейсами. Для каждой из них нашлось свое решение.
Рефакторинг
В первую очередь мы частично переработали код старых тестов, опираясь на более современные паттерны проектирования.
Часть легаси-кода пришлось убрать — его было слишком сложно поддерживать. В другой части мы выловили все слабые места — заменили по дефолту sleep нормальным wait-ерами, вынесли подготовку ко всем тестам в глобальные setup через аннотации тест-раннеров и т.п. Множество мелких шагов позволило сократить проход среднего end-to-end теста с 3-4 до 1-2 минут.
Атомарный подход
Чтобы ускорить создание новых тестов и упростить поддержку старых, мы ушли от громоздких end-to-end-кейсов.
Лично я ничего принципиального против end-to-end-тестирования не имею, однако в том случае, когда нужно проверить один конкретный экран (а то и часть информации на нем), проходить все этапы, начиная с авторизации пользователя, слишком накладно. Представьте, что мы тестируем интернет-магазин и нам требуется проверить только чек, который отправится покупателю после приобретения определенного товара. Вместо того, чтобы выудить из системы только один экран, мы бы заходили по логину и паролю, выбирали товар, подтверждали покупку и т.п. — выполняли бы множество шагов, не связанных с конкретной задачей тестирования. А ведь на каждый шаг требуется время. Даже со всей проведенной оптимизацией запуск end-to-end-теста занимал до 2 минут, в то время как проверка конкретного экрана — всего 10 секунд. Поэтому там, где это было возможно, мы перешли к таким “атомарным” проверкам, обращающимся только к тому экрану, который нас интересует в рамках тест-кейса.
Попутно как раз для сравнения экранов мы внедрили snapshot-тестирование, которое позволяет проверять львиную долю UI. Имея тесты и код приложения в одном репозитории, мы можем в тестах задействовать методы этого приложения, т.е. поднимать любые экраны, которые нужны в этом процессе. Так мы можем находить ошибки на сравнении тестовых снимков экранов с эталонными.
Сейчас snapshot-тестов у нас уже около 300, и их число постепенно растет, поскольку данный подход позволяет существенно сократить время проверки готовой версии перед отправкой ее на продакшн. Весь этот набор тестов запускается автоматически при открытии pull request и прогоняется за 40 минут — так разработчики быстро получают фидбек о проблемах в текущей ветке.
Безусловно, некоторое количество end-to-end-тестов сохранилось. Без них не обойтись там, где требуется проверка больших бизнес-сценариев, но их имеет смысл запускать, когда все детали уже проверены.
Мокирование
Чтобы исключить влияние нестабильного тестового стенда на результат запуска наших тестов, мы запустили мок-сервер. О том, какие решения мы тогда рассматривали и почему выбрали Okhttpmockwebserver, я уже писал.
В результате доля эпизодически падающих из-за внешних причин тестов существенно сократилась.
Kotlin DSL
Параллельно мы сделали тесты более читаемыми.
Те, кто занимаются UI-тестированием, знают, как сложно выискивать истину среди кучи локаторов в длинной “портянке” теста (особенно на той стадии, когда это еще были end-to-end-тесты). В них просто ориентироваться, когда ты на проекте уже два года и даже посреди ночи способен вспомнить, что есть что. Но если ты только пришел, въехать в происходящее — отдельная большая задача. Чтобы новым людям не пришлось каждый раз с ней сталкиваться, мы приняли решение перейти на Kotlin DSL. Он реализуется достаточно просто и имеет простую и понятную структуру. Если раньше тесты состояли из набора одинаковых низкоуровневых вызовов — кликов, ввода текста, скроллов, то теперь все это превратилось в нечто более “бизнесовое” — что-то вроде BDD-подхода. Все видно и понятно.
На мой взгляд, этим мы сделали определенный задел на будущее. Данный проект однажды уже столкнулся с уходом единственного автоматизатора. Для тестов это закончилось не лучшим образом — их просто перестали поддерживать, поскольку порог входа получился слишком высоким. Для понимания такого сухого кода требовалось много времени и определенная квалификация. Мы же переделали тесты таким образом, что на автоматизацию можно будет в любой момент оперативно перебросить людей с других проектов или из ручного тестирования. Простейшие тесты на Kotlin DSL может писать практически любой. Так автоматизаторам можно оставить низкоуровневую реализацию, а на быстрое написание новых простых тестов подключить людей из команды функциональщиков. У них достаточно познаний в бизнес-логике, и проект только выиграет от того, что они будут больше вовлечены в процесс написания автотестов. Kotlin DSL позволяет описывать тест-кейсы именно так, как они хотели бы видеть все проверки, оставляя низкоуровневую реализацию методов за рамками их работы.
В целом это все позволило быстрее увеличивать покрытие автотестами. Если раньше на реализацию нового набора тестов (test suite) уходило 16-20 часов, то с новым подходом, в зависимости от сложности тестов, требуется от 4 до 12 часов (а трудозатраты на поддержку сократились с 16-24 до 8-12 часов в неделю).
Автор статьи: Руслан Абдулин.