Привет! Меня зовут Сергей, я бэкенд разработчик команды SEO в Банки.ру.

В этой статье хочу описать свое знакомство с техникой Trace Based Testing (TBT): 

  • расскажу о концепциях, которые чаще всего используются сейчас в тестировании

  • кратко опишу, что такое distributed tracing 

  • поделюсь опытом работы с TBT. 

Статья — мой субъективный взгляд на технику как бэкенд-разработчика. QA инженеры могут со мной поспорить и, скорее всего, будут правы.

Почему вообще про эту тему рассказывает разработчик, несмотря на присутствие слова testing в названии? Во-первых, чтобы обратить внимание на технику TBT. На профильных ресурсах информации мало и я решил заполнить этот пробел. Во-вторых, чтобы узнать об опыте использования таких инструментов, выслушать мнение комьюнити. Так что добро пожаловать в комментарии ????

Тестирование и разработка

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

Комплексное тестирование повышает качество продукта. А в ряде случаев еще и ускоряет и упрощает Time To Market, что бизнес-заказчиков довольными, а разработчиков мотивированными. 

Если смотреть с точки зрения обычного разработчика жизненный цикл задачи состоит из следующих этапов:

  1. Задача поступает в работу.

  2. Разработчик пишет код и Unit-тесты на него.

  3. Прогоняет тесты, которые ему доступны.

  4. Задача уходит в тестирование.

  5. Разработчик видит результат в продакшене. 

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

 Выполняя следующую задачу, разработчик задумается: «А могу ли я доверять тестированию?». Если ответ отрицательный, то приходится тратить лишнее время на проверку кода своими силами и мониторинг. 

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

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

Рис.1 Зависимость процесса разработки от доверия к тестированию.
Рис.1 Зависимость процесса разработки от доверия к тестированию.

Отмечу, что доверии тестированию — это не про коллег, а про процессы.

Итак, что может повысить мой уровень доверия к процессам тестированию:

  1. Наличие тест-кейсов.

  2. Наличие чек-листов в этих тест-кейсах.

  3. Наличие по этим чек-листам автотестов.

Как тестирование работает сейчас

Никидаем схемку с кусочком простой микросервисной архитектуры.

Рис 2. Простое такое приложение
Рис 2. Простое такое приложение
  1. Сервис web-something выступает в роли API gateway. Он реализует паттерн  API-composition, то есть делает запросы в другие сервисы. Потом формирует HTML и отдает его пользователю.

  2. Content — это не обязательно какие-то новости. Например, это продуктовый API сервис, который возвращает кредитные карты, кредиты, все что угодно.

  3. SEO. После того как web-something получил продукты или какой-то новостной контент, ему нужно насытить эти данные SEO: получить всякие title, meta-tags, description и т.д.

  4. User-rating. Предположим, что web-something ходит в сервис user-rating, чтобы получить отзывы о продуктах или о страховых компаниях. 

Каждый из представленных микросервисов обслуживается своей командой.

Итак, я разработчик команды SEO. Как мне убедиться в том, что фича, которую я запилил, работает правильно? Самый простой способ — передать задачу QA-специалисту, который будет делать проверку по тест-кейсам и чек-листам в ручном режиме. 

Рис 5. Ручное тестирование.
Рис 5. Ручное тестирование.

QA-инженер делает запросы, получает страницу и проверяет.

В принципе это лучше, чем ничего. Но тестировщик — человек, он может ошибаться. 

Этот процесс хочется автоматизировать. Чтобы QA-инженер, вместо того, чтобы заниматься рутиной, писал автоматические тесты и поддерживал их. Такие тесты называются End-to-end тесты или E2E

Рис 6. E2E тесты.
Рис 6. E2E тесты.

End-to-end или E2E тесты и их ограничения

Эти тесты добавляют мне уверенности, ведь автоматизация нивелирует человеческий фактор. Но у автотестов есть ограничения. 

Самое ощутимое — возможность проверять сайд-эффекты.

Рис 7. Трудности тестирования сайд-эффектов.
Рис 7. Трудности тестирования сайд-эффектов.

Допустим, при запросе данных у SEO-сервиса для какого-то определённого URL, сервис кладёт этот URL в карту сайта, в базу данных. После такие URL из базы превращаются в XML-файлы — карты сайта для Search Engines (ботов Google, Yandex и т.д.) Как нам эту фичу проверить?  

Способы, конечно, есть:

1. Подождать, пока команда по сборке XML-карты сайта отработает по крону или самим ее запускать в этих тестах. Представьте, сколько времени это займет…

2. Пойти в базу и посмотреть, что URL действительно добавлен. Но сервисов много, у каждый из них может быть сайд-эффект. 

Другие ограничения E2E-тестирования:

Кроме трудностей с сайд-эффектами у E2E есть другие ограничения:

  1. Тесты прогоняются на всех микросервисах, а значит, все их нужно поднять. Тесты будут тяжелыми и медленными. 

  1. Тесты нужно поддерживать. Специалист QA должен обладать квалификацией, знать какой-то язык программирования.

  1. Чаще всего такие тесты завязаны на UI. Человек сначала протестировал, а потом написал автотесты на основе взаимодействия с UI. Если что-то меняется в вёрстке, тест ломается и его нужно переписывать.

  1. Совсем не очевидна причина поломки тестов. Например, когда на странице пропадает заголовок, падает соответствующий тест. Мы понимаем, что заголовка нет, но не понимаем, почему. Причиной может быть проблема в SEO-сервисе или ошибка в сервисе web-something, которая помешала ему обработать этот ответ. 

  1. Так называемый фидбэк-луп  — промежуток между выполнением задачи разработчиком и нахождением проблемы тестировщиком — очень длинный.  За это время разработчик уже успеет взять другую задачу. Запускать E2E тесты может и сам разработчик, но (см. пункт 1) нужно поднять все микросервисы. Должен ли разработчик тратить на это время? А если тест не актуализирован и его надо доработать?

Для удобства разработки было бы полезно узнавать о проблемах с задачей раньше. Запустить команду — и вот баг уже обнаружен. Никого не ждем.

Компонентные тесты

Такие тесты есть. Компонентные тесты — те же E2E, только тестируем микросервис в изоляции. 

Рис 8. Компонентые тесты.
Рис 8. Компонентые тесты.

Главное ограничение — мы можем сделать правки в тестах, а команда сервиса web-something будет не готова к этим изменениям и получит ошибку.

Интеграционные тесты

Из-за таких ограничений нужны интеграционные тесты и их подтип — контрактные тесты. Они работают с кусочками кода, которые отвечают за взаимодействие сервисов. Поднимать нужно только два, а не систему целиком.  

Рис 9. Интеграционные и контрактные тесты.
Рис 9. Интеграционные и контрактные тесты.

Реализуется это двумя паттернами:

  1. Consumer driven contract tests. Когда команда сервиса web-something участвует в написании тестов для сервиса SEO.

  1. Consumer side contract tests. Противоположный паттерн, при котором команда сервиса-провайдера (в нашем случае SEO) участвует в написании тестов для команд сервиса-консьюмера. У нас это web-something.

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

Поэтому этот процесс автоматизировали и создали инструменты, позволяющие сделать этот декаплинг команд. Например, Pact или Mockoon. 

Суть инструментов проста: команды двух сервисов договариваются о каком-то API -контракте. Контракт засылается в ПО, на его основании, оно предоставляет мок-сервис провайдера, с которым уже будут общаться тесты сервис-консьюмера. 

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

История крутая, потому что если все грамотно настроить, получим очень быстрый фидбэк-луп. Разработчик кликнет на кнопку и тесты прогонятся. Никакие другие сервисы для этого поднимать не нужно.  

Но есть ограничения. Куда без них.

Ограничения интеграционных тестов

Главное ограничение — это моки. Случается, что после тестирования с моковыми данными, на продакшене все равно происходят ошибки. И чем серьёзнее такая ошибка была, тем хуже отношение у разработчика к мокам. 

Чтобы нивелировать такой риск, все равно нужно иметь E2E тесты.

Далее пара субъективных моментов. 

Я считаю, что на практике запустить интеграционные тесты сложно. Мне нужно договориться с коллегами, какие инструменты использовать. Договориться с руководством о том, что мы будем тратить на тесты определенное время. Кроме того, нужно, чтобы тестировщик умел писать тесты. В общем, для разработчика это не самый лучший выход. 

Даже если команда умудрилась эту историю поднять, ее еще нужно поддерживать. Написание тестов должно стать привычкой. Нужно контролировать, что именно изменяется в контрактах, какие тесты пишутся и так далее. Следом возникает вопрос: кто в команде должен это делать и за это отвечать?

Что в итоге. 

  1. Тесты не взаимозаменяемы. Каждый их них отвечает за свой участок и работать они должны вместе.

  2. Реальность далека от идеала. Не всегда получается качественно реализовать все элементы пирамиды тестирования и их поддерживать.

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

Но если даже представить, что у нас есть все способы проверки: и контрактное тестирование, и интеграционные, UNIT, E2E — жизнь все равно вносит свои коррективы. Если тестовая и продакшн среды приближены друг к другу — отлично. Но они никогда не будут идентичными. На проде всегда будут появляться баги, которых не было в тестовой среде. 

Получается, что тестирование не гарантирует отсутствие ошибок, а лишь снижает их вероятность. 

Поэтому я, как разработчик, не могу представить своей жизни без всяких логов, метрик и distributed tracing, в том числе.

Distributed tracing

Суть Distributed Tracing проста — каждому входящему запросу мы присваиваем TraceID, который передается на всех подзапросах. Подзапросы заключаются в так называемые спаны.

В JAEGER это выглядит так: 

У нас есть верхняя строчка — рутовый спан, то есть самый первый запрос. Он исполнился за полторы секунды. В рамках этого рутового запроса было несколько запросов к другим сервисам. Например, к SEO-сервису. Он выполнился за 42 мс. В рамках запроса к SEO был запрос, допустим, к базе данных — 2 миллисекунды.

То есть у всех спанов есть время и ссылка на родительский спан. Помимо этих данных, можно вкладывать другие. Например, добавить SQL-запрос к базе или бизнесовую информацию, которая нужна для тестирования. Главное, не перегружать спаны информацией, потому что трейсы тяжелее, чем логи, и не должны быть их заменой. 

Test driven testing

С одной стороны, у нас есть тестирование со своими инструментами и значительными затратами. С другой, все равно приходится использовать всякие distributed tracing, logging, metrics. Прикручивать их к приложению и как-то поддерживать. А раз нам все это нужно, почему бы не использовать одно для другого. То есть Distributed Tracing для тестирования. Так и получается TBT.

Что такое Test Driven Testing?

Если упрощать, это E2E тесты, которые используют трейсы. Можем ли мы в E2E тестах просто взять сходить в trace-datastore, где хранятся наши трейсы? Проверить, есть ли в каком-то спане нужный нам атрибут с нужным значением? Разработчики и QA, работая вместе, могут все ????.

Инструмент Tracetest

Tracetest  — пример TDT инструмента. Это ПО, которое можно поднять как сервис или запустить на локальном компьютере. С его помощью E2E и интеграционные тесты создаются в несколько кликов.

У Tracetest хорошее демо-приложение. На его примере можно посмотреть, как все работает. 

У приложения три основных элемента: 

Demo app
Demo app
  1. API — небольшой магазинчик-покешоп, реализован с помощью CRUD. 

  2. Jaeger, то есть все трейсы приложения собираются.

  3. Tracetest-сервис — сам tracetest.


Зайдем в приложение и добавим покемона.

Нажмем OK и пойдем в Jaeger.

Находим trace, который был создан при отправке этой формы, заходим в него.

Видим, что у нас и в URL  и на странице есть TraceID. Копируем его и переходим в UI самого Tracetest.

Нажимаем Create и создаем тест. 

У нас есть выбор, как этот тест инициировать. Через HTTP Request или CURL-команду, которые выполнят запрос или запуск команды. Это создаст новые трейсы в Trace Store. 

Но мы хотим попробовать прогнать тесты по трейсу, который мы создали ранее. Для этого выберем триггер по TraceID, нажмем Next. На следующем экране нас попросят ввести название переменной для этого Trace

Можно запускать. В следующий момент произойдет фетчинг трейса из трейс-стора, и он его отобразит. Здесь два главных элемента: этот спан и обращение к базе данных.

Выберем трейс-спан с POST-запросом и попробуем добавить в тест спецификацию.

При создании спецификации он сразу подставляет нам селектор для этого спана. 

Здесь мы можем добавлять сколько угодно ассертов.

Я добавил ассерт на время спана и указал, что его нужно проверить и убедиться, что оно меньше 25 миллисекунд.

На спане написано 23 мс, поэтому верификация пройдёт успешно. Но если в ассерте укажем 20 миллисекунд, тест провалится.

После написания этого теста процесс можно автоматизировать. Если перейти на вкладку automate, здесь будет YAML-файл. Скачиваем его и запускаем отдельно.

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

Плюсы TDT подхода 

  1. Даже если не рассматривать использование именно Tracetest, а только технику TBT, такие тесты легче писать. У тестировщика перед глазами будет трейс. Он будет понимать, что происходит в системе. 

  2. Эти тесты можно прогонять даже на проде, так как они используют трейсы и не делают дополнительных запросов. То есть, моки для них не нужны. Хотя такие тесты уже можно будет назвать мониторингом.

  3. Тесты можно написать под сайд-эффекты, проверить отправку событий или сохранение в базу данных.

  4. Самое главное — такие тесты сближают тестирование и разработку. Потому что обе команды начинают использовать один инструмент, а значит, говорить на одном языке.

Минусы TDT

  1. Говорить про эту технику начали давно, но зрелой ее еще не назовешь. Поэтому к запуску нужно подходить внимательно, обязательно делать пилот. 

  2. TDT это все-таки E2E тесты, они содержат все их минусы. Если делаем тестирование на тестовой среде, нужно поднимать все микросервисы.

Когда использовать подобные тесты?

Один из случаев — это когда разработчик чувствует, что контрактное тестирование  уже пора внедрять, но по каким-то причинам сделать это сложно. Тогда можно поставить Tracetest даже на локальной машине, написать эти тесты и прогонять. Старт достаточно легкий.

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

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


  1. xxori
    20.12.2023 06:14

    Спасибо за интересный подход с примерами. В итоге это Trace Based Testing или Test Driven Testing? Можно сорсы для статьи, пожалуйста, или доки Tracetest хватить попробовать потыкать?


  1. Processor2
    20.12.2023 06:14

    Задача поступает в работу.

    Разработчик пишет код и Unit-тесты на него.

    Прогоняет тесты, которые ему доступны.

    Задача уходит в тестирование.

    Разработчик видит результат в продакшене.

    Если четвертый пункт, тестирование, вдруг не работает или работает недостаточно хорошо, разработчик получает баг.

    Разработчик получает баг, только в том случае, когда плохо сработал во 2 пункте) Тестирование не делает багов, оно делает репорты на них)