Добрый день! Я хотел бы представить новый инструмент для end-to-end тестирования микросервисов – Catcher
Зачем нужно e2e тестирование? Мартин Фаулер рекомендует избегать его в пользу более простых тестов.
Однако, чем выше находятся тесты — тем меньше переписывать. Юнит тесты переписываются почти полностью. На функциональные тесты также приходится тратить свое время в случае серьезного рефакторинга. End-to-end тесты же должны проверять бизнес логику, а она меняется реже всего.
К тому же, даже полное покрытие тестами всех микросервисов не гарантирует их корректного взаимодействия. Разработчики могут неправильно имплементировать протокол (ошибки в наименовании/типе данных).
Или реализовать новый функционал полагаясь на схему данных из документации, а на продуктовом окружении получить сюрприз в виде ошибок несоответствия схем: бардак в данных либо кто-то забыл обновить схему данных.
И тесты каждого из задействованных сервисов будут зеленые.
Действительно. На моем предыдущем месте работы решили, что тратить время на разворачивание автоматических тестов это слишком долго, сложно и дорого. Система не большая (10-15 микросервисов с общей кафкой). CTO решил, что «тесты не важны, главное чтобы система работала». Тестировали вручную на нескольких окружениях.
Как это выглядело (общий процесс):
А теперь немного дегтя в эту бочку с шоколадом: на большинство тестов нужно было создавать пользователей, потому что переиспользовать существующих сложно.
Во-первых, из-за того, что система распределенная — у нескольких сервисов были свои базы данных, в которых лежала информация о пользователях.
Во-вторых, кафка использовалась для постоянного хранения данных. Т.е. даже если информацию удалить/изменить в базе данных, сервис при перезагрузке все-равно прочитает ее обратно.
Как выглядела регистрация нового тестового пользователя (приблизительно):
Супер, пользователь зарегистрирован! Теперь еще немного дегтя: для некоторых тестов нужно больше чем 1 тестовый пользователь. И иногда с первого раза тесты не проходят.
А как происходит проверка нового функционала и подтверждение со стороны бизнес-команды?
Все то же самое нужно повторить на следующем окружении.
Нужно ли говорить, что через некоторое время начинаешь ощущать себя мартышкой, которая только и делает что нажимает кнопки, регистрируя пользователей.
Еще у некоторых разработчиков (обычно у фронт-энда) были проблемы с подключением к кафке. И с багом в терминале при строке 80+ символов (не все знали про tmux).
Плюсы:
Минусы:
Если вы дочитали до сюда кивая головой и приговаривая: «да, отличный процесс, парни знают что делают», то дальше вам будет не интересно.
Самодельные e2e тесты бывают двух типов и зависят от того, какой из программистов был более свободен:
Звучит неплохо. Проблемы?
Да, такие тесты пишутся на том, что знает тот, кто их пишет. Обычно это скриптовые языки вроде руби или питон, которые позволяют быстро и просто написать подобного рода вещь. Однако, иногда можно наткнуться на ворох баш-скриптов, Си или что-то более экзотическое (я потратил неделю, переписывая велосипед на баш скриптах на питон, потому что скрипты уже были не расширяемыми и никто толком не знал как они работают и что тестируют).
Пример проекта здесь
Плюсы:
Минусы:
Конечно, достаточно посмотреть в сторону BDD. Есть Cucumber, есть Gauge.
Если кратко – разработчик описывает бизнес сценарий на специальном языке, после реализует шаги сценария в коде. Язык как правило человекочитаемый и предполагается что его будут читать/писать не только разработчики, но и менеджеры проектов.
Сценарии вместе с реализацией шагов также находятся в отдельном проекте и запускаются сторонними продуктами (Cucumber/Gauge/…).
Сценарий выглядит так:
Customer sign-up
================
* Go to sign up page
Customer sign-up
----------------
tags: sign-up, customer
* Sign up a new customer with name "John" email "jdoe@test.de" and "password"
* Check if the sign up was successful
И реализация:
@Step("Sign up as <customer> with email <test@example.com> and <password>")
public void signUp(String customer, String email, String password) {
WebDriver webDriver = Driver.webDriver;
WebElement form = webDriver.findElement(By.id("new_user"));
form.findElement(By.name("user[username]")).sendKeys(customer);
form.findElement(By.name("user[email]")).sendKeys(email);
form.findElement(By.name("user[password]")).sendKeys(password);
form.findElement(By.name("user[password_confirmation]")).sendKeys(password);
form.findElement(By.name("commit")).click();
}
@Step("Check if the sign up was successful")
public void checkSignUpSuccessful() {
WebDriver webDriver = Driver.webDriver;
WebElement message = webDriver.findElements(By.className("message"));
assertThat(message.getText(), is("You have been signed up successfully!"));
}
Полный проект здесь
Плюсы:
Минусы:
Разумеется, чтобы упростить процесс.
Разработчик пишет только сценарии в json/yaml, а Catcher их выполняет. Сценарий состоит из последовательно выполняемых шагов, например:
steps:
- http:
post:
url: '127.0.0.1/save_data'
body: {key: '1', data: 'foo'}
- postgres:
request:
conf: 'dbname=test user=test host=localhost password=test'
query: 'select * from test where id=1'
Catcher поддерживает jinja2 шаблоны, так что можно использовать переменные вместо зашитых значений в примере выше. Глобальные переменные можно хранить в инвентарных файлах (как в ансибле), подтягивать из окружения и регистрировать новые:
variables:
bonus: 5000
initial_value: 1000
steps:
- http:
post:
url: '{{ user_service }}/sign_up'
body: {username: 'test_user_{{ RANDOM_INT }}', data: 'stub'}
register: {user_id: '{{ OUTPUT.uuid }}'
- kafka:
consume:
server: '{{ kafka }}'
topic: '{{ new_users_topic }}'
where:
equals: {the: '{{ MESSAGE.uuid }}', is: '{{ user_id }}'}
register: {balance: '{{ OUTPUT.initial_balance }}'}
Дополнительно можно запускать проверочные шаги:
- check: # check user’s initial balance
equals: {the: '{{ balance }}', is: '{{ initial_value + bonus }}'}
Также можно запускать одни скрипты из других скриптов, что отлично сказывается на чистоте и повторном использовании кода (включая запуск только части шагов через систему тегов, отложенный запуск и т.п. плюшки).
include:
file: register_user.yaml
as: sign_up
steps:
# .... some steps
- run:
include: sign_up
# .... some steps
Вставка и использование скриптов могут решить проблему ожидания ресурса (ждать сервис, пока он стартует).
Помимо готовых встроенные шагов и дополнительного репозитория) есть возможность писать свои модули на питоне (просто наследуя ExternalStep) или на любом другом языке:
#!/bin/bash
one=$(echo ${1} | jq -r '.add.the')
two=$(echo ${1} | jq -r '.add.to')
echo $((${one} + ${two}))
и использование:
---
variables:
one: 1
two: 2
steps:
- math:
add: {the: '{{ one }}', to: '{{ two }}'}
register: {sum: '{{ OUTPUT }}'}
Сценарии помещаются в докер файл и запускается через CI.
Также этот образ может быть использован в Marathon/K8s для тестирования существующего окружения. На данный момент я работаю над бэкэндом (аналог AnsibleTower), чтобы сделать процесс тестирования еще проще и удобнее.
Плюсы:
Минусы:
Когда я писал этот инструмент, я просто хотел сократить время, которое я обычно трачу на тесты. Так получилось, что в каждой новой компании приходится писать (или переписывать) такую систему.
Однако инструмент получился гибче, чем я предполагал. Если кого-то заинтересует статья (или сам инструмент), я могу рассказать, как использовать Catcher для организации централизованных миграций и обновления системы микросервисов.
Как мне указали в комментариях, тема не раскрыта.
Попытаюсь указать здесь наиболее спорные тезисы.
tests
) и запускаются каждый раз при изменении кода на CI. А e2e тесты это отдельный проект, они обычно выполняются дольше, тестируют взаимодествие всех участвующих сервисов и не знают ничего о коде вашего проекта (черный ящик).К сожалению, не доступен сервер mySQL