Меня зовут Виктор Мясников, я отвечаю за качество продукта в «Юле». Я закончил Бауманку и хотел строить ракеты, но с ними не вышло, поэтому теперь профессионально строю «велосипеды» для 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? У него есть тело, с которым есть проблемы: 

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

  2. В файле могут находиться какие-то динамические элементы, например, 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, и теперь вместо комментариев — катим и протестировано — у нас есть ссылка на отчёт. Переходя по ней, каждый в команде может посмотреть что именно там происходит.

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

Комментарии (2)


  1. Boeing_777
    27.12.2022 17:53
    +1

    после вот этого

    * assign
    | UUID | #{fun:getUUID()} |
    | a | #{1+1} |

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

    r = request.get(url)

    assert r.status_code == 200

    которые и выглядят не сложнее того же Геркина и развиваться позволяют с большей широтой


    1. 13vitia Автор
      28.12.2022 22:11
      +1

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

      Что касаемо развития - всегда есть практически безграничное количество задач на "покодить" на любой уровень.