Меня зовут Виктор Мясников, я отвечаю за качество продукта в «Юле». Я закончил Бауманку и хотел строить ракеты, но с ними не вышло, поэтому теперь профессионально строю «велосипеды» для QA, а ещё люблю уничтожать рутину. Я расскажу, как мы проектировали BDD-фреймворк и зачем он вообще нам понадобился.
Зачем это всё?
«Юла» — это сервис объявлений, представленный в iOS, Android, ВКонтакте и вебе. Когда мы делали BDD-фреймворк, у нас была отдельная QA-команда, которая тестировала API. Чтобы вы представляли ситуацию, я познакомлю вас с одним из тестировщиков этой команды. Все имена вымышлены, а совпадения случайны, но это поможет вам понять особенности, а где-то, быть может, увидеть совпадения с вашей ситуацией.
«Попробовал и не получилось»
Давайте познакомимся с Андреем — ручным тестировщиком. Сразу оговорюсь, Андрей – собирательный образ ручного тестировщика, история которого может показаться вам знакомой.
Он уже более двух лет профессионально тестирует API. Андрей знает множество техник тест-проектирования и использует их в ежедневной работе. Естественно, он пробовал автоматизацию. Кто же из нас хотя бы раз не пытался её попробовать, когда только начинал? Изначально Андрей очень хотел автоматизировать, но, к сожалению, что-то пошло не так. Поэтому Андрей сделал вывод: «Автоматизация не нужна и всё только усложняет. Надёжнее тестировать самому».
Клики мышкой, больше кликов!
Каждый день наш Андрей приходит на работу и тестирует задачи. Конечно, во всех командах и компаниях путь решения задач разный, но любая задача проходит основные этапы, например, тест на ветке и тест на релиз-кандидате. Как это выглядит?
Андрей берет Postman и кликает, и кликает, и кликает, и … Вы поняли общую идею.
Затем на следующем этапе он опять кликает, кликает, кликает, и так проходит его рабочий день.
Так как автоматизация изначально не получилась, это и есть рутина, которая накапливается и увеличивается. Андрей не может «переварить больше», при этом он продолжает так делать каждый день.
Да что в этом плохого?!
Казалось бы, ну и ладно. Но при таком подходе придётся столкнуться с несколькими проблемами.
«Дай последнюю коллекцию»
У каждого человека в нашей команде тестирования API была своя Postman-коллекция. Тестировщикам надо было как-то обмениваться ими: кто-то ушёл в отпуск, кто-то протестировал чужой релиз и надо об этом сообщить другому человеку. В итоге всё общение в команде сводится к фразе: «Дай последнюю коллекцию».
Комментарий про Postman
Да, у Postman есть своё облачное хранилище. Оно упрощает обмен, но использовать его мы не могли: это небезопасно, а сейчас и вовсе может быть отключено.
И действительно, всё так и работало: коллекции пересылали друг другу в Slack, Telegram или другом мессенджере. Их накапливалось огромное количество — номера версий переваливали далеко за сотню.
✅ Протестировано
Что же происходит в Jira при таком подходе? Происходит примерно следующее. Отчет о тестировании выглядит вот так:
В Jira — бардак, и непонятно, какую полезную информацию команда может из этого получить. Наверное, такие комментарии хоть раз оставлял каждый QA. И больше скажу, я так же делал, когда начинал. Эти отчёты — действительно проблема, потому что никто не может воспроизвести, кто и что делал.
Что же произошло с нашим Андреем? Плохое обучение автоматизации его демотивировало. Кликать ему надоело, и он расстроился. Сидел и думал: «Автоматизация не получилась, мне она не нужна. И что теперь делать?». С этим барьером сталкиваются многие: автоматизация однажды «не зашла», а рутины уже слишком много.
Три важных НЕ
Изначально, когда мы проектировали BDD-фреймворк, ставили задачу не испугать нашего Андрея. Он больше не должен был бояться автоматизации. Это было самое важное.
Второе: не надо учить его программировать. Иначе опять натолкнёмся на барьер: у Андрея уже когда-то не получилось.
И третий фактор: не изобретать велосипедов. Я, правда, их очень люблю, но в этот раз обошлись без них.
Всё для команды
Поскольку команда у нас уже была, мы выбирали технологии и делали фреймворк под неё. Посмотрев на опыт разных компаний, решили использовать подход BDD, а в качестве реализации взяли Cucumber. А поскольку я и команда автоматизации уже знали Java, то вопрос выбора инструментов отпал сам собой.
А теперь за дело
Проектирование будущего взаимодействия начали с метода GET, как и изучение методов HTTP-запроса. Андрей сказал: «Мне ничего не понятно, но давайте пробовать». Ему стало интересно, что же такое мы можем ему предложить. Да и усталость от ручного кликанья накопилась.
Часть 1. GET
За основу мы взяли уже используемый командой Postman. Основное, что нужно было описать:
Тип запроса — у нас это GET.
Адресная строка. Тут всё просто, указываем её в кавычках.
Заголовки. Это пары ключ-значение, удобно использовать табличный вид:
add headers |key|value|
.Кнопка «отправить».
Проверка статус-кода ответа:
status code 200
.
Для зануд, любителей придраться к мелочам, или просто подробнее почитать про BDD подход можно начать с неплохой статьи тут, или поискать что-то самому, начав, как полагается, с вики.
В результате мы научились на языке Gherkin описывать действия, которые QA делал в Postman. Получили описание поведения системы, которое мы ожидаем.
Естественно, одним GET сыт не будешь, поэтому следующий запрос у нас POST.
Часть 2. POST
Чем POST отличается от GET? У него есть тело, с которым есть проблемы:
Оно может быть сколько угодно большим по размеру, поэтому просто так вписывать его в тест не очень-то хотелось, иначе тот будет нечитабельный. Мы решили класть тело в JSON-файл, который лежит в определённом месте в проекте и будет отсылаться вместе с запросом. Для упрощения мы указываем только его имя.
В файле могут находиться какие-то динамические элементы, например, ID. Конечно его можно оставить в проекте в таком виде, но будет неудобно работать. Поэтому мы представили JSON-файл в виде так называемого шаблона. Это значит, что в момент выполнения во все динамические поля подставляются актуальные значения. А при проектировании теста они будут храниться в виде ссылки. Таким образом, перед тестом мы можем сказать, что у нас есть ID, который в момент выполнения заменится на актуальное значение. Естественно, наши тесты должны выполняться в несколько потоков, поэтому мы сделали потокобезопасное хранилище таких переменных, где выполняются всего две операции:
put
иget
.
С помощью такой нехитрой манипуляции мы можем делать динамические элементы в наших JSON-файлах.
Часть 3. Проверки
У нас уже есть проверка статус-кода наших запросов, но это не совсем честная проверка, ведь нам интересно проверять сами данные. Поэтому мы взяли язык запросов в JSONPath, который позволяет находить элементы в структуре JSON-документа. У нас есть сам документ, и по $
мы можем попасть в его корень, выполнить запрос $.id
и получить тот элемент, который нам нужен, то есть ID конкретно в этом JSON-файле. А значит, мы можем сделать проверку, которую несложно реализовать. Подробнее почитать про JSONPath можно в одном из первоисточников тут.
Такая реализация позволила Андрею написать первый набор автотестов. Этого достаточно, чтобы начать покрывать наше API, но этим дело не ограничилось — у нас же несколько стендов.
Часть 4. Стенды
Как ребята («Андрей») поступали в Postman со стендами? Они заводили себе несколько окружений, например, dev
, release
, и туда через переменные подставляли нужные данные.
Если гуглить по фразе «как сделать мультистендовость», вы практически везде будете находить одно и то же решение: заведите два файла dev.properties
и release. properties
.
Мы тоже так сделали, потому что это простое решение. Но когда отдали файлы Андрею и команде, то заметили, что в них начали появляться вот такие записи:
Оказалось, что набор цифр — это секреты конкретного стенда. Периодически они менялись, нам приходилось идти в репозиторий, находить файлы и менять секреты. У других QA в старых ветках это всё утрачивало актуальность, им приходилось постоянно актуализировать рабочие ветки, что усложняло работу. И чем больше таких секретов становилось, тем сложнее было это поддерживать. Были, конечно, экзотические варианты решения — менять property-файлы «на лету», но мы нашли более изящное решение — Vault.
Vault — это защищенный сервис, хранилище типа ключ-значение. В нём мы завели столько папок, сколько было стендов (в примере выше — dev и release), и занесли туда ключи, хосты и так далее подробнее почитать про Vault можно тут и тут ). Это позволило вывести секреты из проекта, и теперь менять их можно без нашего участия. Это важно потому, что секрет может менять, например, команда эксплуатации в целях безопасности. И теперь эта команда знает, где лежат секреты у нас и у них. Подчеркну — это безопасно. Секреты не хранятся в проекте, в репозитории их нет. Таким образом, мы нигде их не раскрываем и нам больше не нужно их поддерживать вручную.
Часть 5. Всё же без велосипедов
Предложенная реализация понравилось Андрею и его команде: работать стало проще, и ребята стали приходить за новыми возможностями.
Первый запрос поступил от другого члена команды: дать возможность считать хеши. Раньше он делал это онлайн MD5-генератором, что было, во-первых, не совсем безопасно, а во-вторых, не очень удобно. Этот запрос был простым в решении.
Второй запрос: нужны новые UUID. Оказывается, у ребят была открыта папка UUID-генератора в Chrome, и каждый раз они генерировали их вручную. С этим тоже может справиться BDD-фреймворк.
А вот третий запрос заставил задуматься. Меня попросили посчитать сумму в проекте. Возник вопрос: «Хочется ли нам поддерживать всю арифметику самостоятельно?» Ведь если нужна сумма, значит скоро появится разность, умножение, деление и вообще вся базовая арифметика. А это трудоёмкая задача. Я обещал, что обойдёмся без велосипедов, поэтому начал искать решение, которое закроет все хотелки сразу.
Что мы нашли? Скриптовый движок JEXL, который предоставляет готовую арифметику и возможность пробросить любую функцию на Java (подробнее почитать про JEXL можно тут).
Интеграция JEXL
Интеграция очень простая: мы добавили его в наш pom-файл, объединили контексты и вызываем движок JEXL там, где нам надо. Как я уже писал, наш контекст реализует две базовые функции: get
и put
. JEXL работает похожим образом, у него есть два метода: get
и set
.
JexlContext jc = new JexlContext() {
@Override
public Object get(String s) {
return Context.getValue("_" + s);
}
@Override
public void set(String s, Object o) {
Context.put("_" + s, o);
}
@Override
public boolean has(String s) {
return Context.has(s);
}
};
Мы наш контекст объединили с JEXL-контекстом. Таким образом, теперь наши изначальные переменные — это переменные JEXL, и тесты умеют делать вот что:
* assign
| UUID | #{fun:getUUID()} |
| a | #{1+1} |
Мы можем написать функцию генерирования UUID, отдать её в тест, и человек просто её вызовет. Аналогичным образом заработала арифметика: теперь наши тесты могут выполнять все четыре операции без нашего участия. Правда, Андрей сказал, что это очень сложно. И это действительно было так. Пришлось потрудиться и объяснить, а потом написать несколько статей в Confluence, как это работает. Но по прошествии буквально нескольких дней тесты стали обрастать такими проверками и всё поехало куда быстрее.
Андрею стало легче работать. Он преодолел барьер и почувствовал, что может автоматизировать тесты, которые будут ему реально помогать.
Итоги
За первые три месяца мы получили вот такой прирост автотестов у команды:
За три месяца количество автотестов выросло вчетверо без ущерба для команды, релиза и практически с бесплатным ретестом.
Внимательные читатели вспомнят про коллекции. Ребята, наконец, удалили их и в качестве источника правды начали использовать тесты. Хотите посмотреть, как реально работает бэкенд? Проще всего это сделать в тестах.
Бонусом мы получили понятную отчётность. Естественно, подключили Allure, и теперь вместо комментариев — катим и протестировано — у нас есть ссылка на отчёт. Переходя по ней, каждый в команде может посмотреть что именно там происходит.
Вот так мы получили основу нашего фреймворка и команду, которая может автоматизировать без знания кода и без ущерба для себя и других.
Boeing_777
после вот этого
* assign
| UUID | #{fun:getUUID()} |
| a | #{1+1} |
я подумал, что вместо программирования на обычном ЯП ваши ребята теперь программируют на вашем придуманном языке -) не знаю на сколько это все полезно и эффективно. Мне кажется проще показать условные:
r = request.get(url)
assert r.status_code == 200
которые и выглядят не сложнее того же Геркина и развиваться позволяют с большей широтой
13vitia Автор
Все конечно хорошо и правильно, вот только внедрить в команде, где много ребят, получивших негативный опыт в автоматизации, такое очень сложно. В той обстановке и команде геркин позволил нам быстрее расти по всем показателям и приблизиться к текущей концепции фулл-стек QA.
Что касаемо развития - всегда есть практически безграничное количество задач на "покодить" на любой уровень.