Мы находимся на пороге новой эры ODD — разработки на основе наблюдаемости. В ней на первый план выходит применение инструментария бэкенд-кода в качестве утверждений для проведения тестов и культура тестирования на основе трассировки. Используя Tracetest, бэкенд-разработчики не просто генерируют E2E-тесты из трассировок OpenTelemetry, они меняют подход к обеспечению качества и повышению скорости даже в самых сложных приложениях.

Команда VK Cloud перевела руководство по инструментам для кода и самостоятельному запуску тестов на основе трассировки. Полный исходный код из статьи можно найти на GitHub.

Настоящее: разработка через тестирование (TDD)


Самые традиционные методы тестирования подразумевают, что сначала, исходя из требований к ПО, пишется код, а потом — интеграционные или E2E-тесты, с помощью которых проверяют его работоспособность. При разработке через тестирование (TDD) разработчики создают сценарии тестирования до написания кода, чтобы обеспечить соблюдение требований к приложению. С TDD цикл разработки ПО кардинально меняется. Согласовав требования, ваша команда:

  1. Добавляет тест, который считается пройденным, только если эти требования соблюдены.
  2. Запускает тест и видит, что он не пройден, как, собственно, и должно быть.
  3. Пишет самый простой код, при котором тест будет пройден.
  4. Снова запускает тест и видит успешный результат.
  5. По мере необходимости рефакторит код, чтобы повысить эффективность, удалить дублирующиеся фрагменты, разбить код на порции поменьше и так далее.

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

Сложности создания и выполнения тестов TDD в бэкенд-разработке


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

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

Если в инфраструктуре активно используются такие ресурсы как бессерверные функции или API-шлюзы, перед вами стоит еще более трудная задача, ведь они по умолчанию являются эфемерными и изолированными. Как получить логи из Lambda, которая уже не существует?

При выполнении E2E-тестов могут возникать сценарии, в которых видно, что CI/CD-пайплайн не прошел тест, но непонятно, где или почему. Часто в поисках ответа вам придется бродить по бесконечному лабиринту, особенно если вы проверяете работоспособность микросервисов.

Если вы не можете определить, где тест не пройден — между микросервисом A и B, или Y и Z, — то для разработки кода ваши сценарии тестирования оказываются бесполезными инструментами. Вы просто пытаетесь обойти вслепую загадочные препятствия.

Как добавлять бэкенд-тесты в среду TDD


Чтобы показать сложности интеграционного и E2E-тестирования в распределенных средах, возьмем в качестве простого примера проект Node.js. Чтобы просто установить инструменты тестирования, нужно добавить в базу несколько новых библиотек: MochaChai и Chai HTTP. Потом для тестов сгенерировать mock-данные, которые хранятся в репозитории, то есть для тестирования увеличить в нем количество файлов и папок, еще сильнее запутав и без того сложную структуру. А потом, чтобы добавить один-единственный тест, нужно написать немаленький кусок кода:

const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
const app = require('../server');
const should = chai.should();
const expect = chai.expect;

const starwarsFilmListMock = require('../mocks/starwars/film_list.json');

describe('GET /people/:id', () => {
  it('should return people information for Luke Skywalker when called', done => {
    const peopleId = '1';
    chai
      request(app)
      .get('/people/' + peopleId)
      .end((err, res) => {
        res.should.have.status(200);
        expect(res.body).to.deep.equal(starwarsLukeSkywalkerPeopleMock);
        done();
    });
  });
});

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

Будущее: разработки на основе наблюдаемости с помощью Tracetest


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

Именно так можно обнаружить и зафиксировать опасные «неизвестные неопределенности» разработки — точки сбоев, для которых вы не могли создать тесты, потому что понятия о них не имели.

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

У распределенных трассировок огромные преимущества в области ODD. Они помогают вам и вашей команде:

  • Понять весь жизненный цикл HTTP-запроса при его прохождении по распределенной инфраструктуре. Видеть, успешен ли он и где конкретно происходит сбой.
  • Отслеживать проблемы или создавать новые тесты, ничего не зная о системе и не создавая новый код.
  • Решать проблемы производительности на уровне кода.
  • Выполнять тесты на основе трассировки непосредственно в продакшне.
  • Выявлять и устранять «неизвестные неопределенности» системы, которые, возможно, проскочили даже мимо сложного TDD.

Как добавить инструментарий OpenTelemetry в бэкенд-код


Платформа Tracetest интегрируется со многими хранилищами данных трассировки, такими как Jaeger, Grafana Tempo, Opensearch и SignalFX. Самый быстрый способ настроить распределенные трассировки — это добавить в базу кода SDK OpenTelemetry для конкретного языка. У популярных языков также есть auto-instrumentation, как в примере с Node.js, который мы создадим ниже.

Если у вас уже есть хранилище данных трассировки, например, Jaeger, Grafana Tempo, Opensearch или SignalFX, воспользуйтесь нашими подробными инструкциями по быстрому подключению Tracetest к экземпляру.

Например, нам нужно всего лишь несколько строк кода, чтобы добавить трассировку в проект на основе Node.js/Express:

const opentelemetry = require('@opentelemetry/sdk-node')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')

const sdk = new opentelemetry.NodeSDK({
  traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4318/v1/traces' }),
  instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()

Этот код функционирует как оболочка для остального приложения, запуская трейсер и отправляя трассировки в коллектор OpenTelemetry, который, в свою очередь, передает их в Tracetest. Для этого нужно несколько дополнительных сервисов и конфигураций, но мы можем упаковать все в два файла Docker Compose для всей экосистемы.

В репозитории Tracetest на GitHub можно найти проект на основе Node.js/Express и примеры интеграции с другими хранилищами данных трассировки.

Чтобы быстро найти нужный пример, выполните команду:

git clone https://github.com/kubeshop/tracetest.git
cd tracetest/examples/quick-start-nodejs/
docker-compose -f docker-compose.yaml -f tracetest/docker-compose.yaml up --build

Как использовать Tracetest для ODD


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

В системе macOS установка Tracetest CLI происходит в один шаг:

brew install kubeshop/tracetest/tracetest

Примечание. Дополнительную информацию можно найти на странице «Загрузки».

Мы рекомендуем в соответствии с официальной документацией установить Tracetest CLI+сервер: это поможет настроить источник данных трассировки и генерировать все файлы конфигурации, необходимые для сбора трассировок и создания новых тестов. Более подробное объяснение можно прочитать у нас в документации. Можно также прочитать статью о подключении Tracetest к OpenTelemetry Collector.

После того как вы настроили Tracetest, откройте в браузере http://localhost:11633, чтобы проверить пользовательский веб-интерфейс.

Создавайте тесты визуально


В пользовательском интерфейсе Tracetest нажмите выпадающий список Create и выберите Create New Test. Здесь мы создаем HTTP-запрос. Нажмите Next, придумайте имя и описание для теста.

В этом простом примере вы просто используете команду GET
для приложения, которое работает по адресу http://app:8080.

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

Как формулировать утверждения для каждой отдельной точки HTTP-транзакции


Перейдите на вкладку Test, нажмите Add Test Spec и сформулируйте утверждения — это фундамент, на котором строится внедрение ODD и отслеживание общего качества приложения в разных средах.

Чтобы создать утверждение на базе GET/спана нашей трассировки, выберите этот спан в представлении «граф» и нажмите Current span в модальном окне Test Spec. Или просто скопируйте селектор этого спана с помощью Tracetest Selector Language:

span[tracetest.span.type="http" name="GET /" http.target="/" http.method="GET"]

Ниже добавьте атрибут attr:http.status_code и ожидаемое значение, то есть 200. Можно добавлять и более сложные утверждения, например, проверить, выполняется ли спан меньше чем за 500 мс. Для этого добавьте новое утверждение для  attr:http.status_code, выберите < и добавьте ожидаемое значение — 500 мс.

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

Потом нажмите Save Test Spec, после чего появляется Publish, — вот вы и создали свое первое утверждение.

Создайте YAML для теста в Tracetest


Создав спецификацию теста, нажмите на шестеренку рядом с Run Test, потом Test Definition: откроется модальное окно для просмотра и загрузки файла .yaml, который понадобится для запуска этого теста с помощью Tracetest CLI:

Загрузите файл .yaml, назовите его test-api.yaml и сохраните в корневой директории приложения, которое мы взяли для примера.

Как выполнить тест в Tracetest CLI


Конечно, можно выполнить тест в GUI, нажав кнопку Run Test. Тест будет выполнен на основе распределенной трассировки и скажет, подтвердилось ли утверждение. Автоматизация позволяет с помощью Tracetest обнаруживать регрессии, проверять SLO сервисов и делать много другого. Так что давайте продемонстрируем инструментарий CLI.

Вернитесь к терминалу и проверьте конфигурацию Tracetest CLI:

tracetest configure

[Output]
Enter your Tracetest server URL [http://localhost:11633]: http://localhost:11633

Запустите тест, используя дефиницию, которую вы создали и загрузили выше:

$ tracetest test run -d test-api.yaml -w

✔ Example Test One (http://localhost:11633/test/ycmoH254g/run/7/test)

CLI покажет, выполнен ли тест корректно, но не покажет, пройден он или нет. Для этого кликните по ссылке в CLI output или вернитесь в Tracetest.

Тест пройден, ведь вы тестируете код состояния HTTP-запроса, который должен быть равен 200, и продолжительность должна быть гораздо меньше 500 мс.

Теперь посмотрите, как ODD и тестирование на базе трассировки помогает находить ошибки в коде, экономя вам время на написание дополнительных тестов. Добавьте  setTimeout, который не позволяет приложению выдавать ответ по крайней мере 1 000 мс.

const express = require("express")
const app = express()
app.get("/", (req, res) => {
  setTimeout(() => {
    res.send("Hello World")
  }, 1000);
})
app.listen(8080, () => {
  console.log(`Listening for requests on http://localhost:8080`)
})

Повторите тест в CLI, потом перейдите в Web UI. Там вы увидите, что утверждение не подтвердилось из-за setTimeout. Это значит, что продолжительность спана превышает 1 с:

Заключение


Испытайте Tracetest на приложениях и инфраструктуре с трассировками. Воспользуйтесь нашим кратким пошаговым руководством, в котором мы рассказываем об инструментарии CLI и сервере Tracetest. После этого вы в простом UI начнете генерировать ценные E2E-тесты быстрее, чем когда-либо ранее, увеличите тестовый охват, откажетесь от тестирования вручную и выявите проблемы, о существовании которых вы даже не подозревали.

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