Разделы портала

Онлайн-тренинги

.
Разбираем на части E2E на реальном примере
06.11.2024 00:00

Автор: Баз Дейкстра (Bas Dijkstra)
Оригинал статьи
Перевод: Ольга Алифанова

В последние пару лет я все чаще и чаще говорю о тестировании контрактов – как читая лекции, так и работая с клиентами. Контрактное тестирование обещает снизить зависимость от длинных, медленных и дорогих end-to-end тестов. Как это работает на практике?

И в целом, как командам перестать так сильно полагаться на медленные и дорогие E2E-тесты?

Примечание: я не говорю, что вам нужно избавиться от всех E2E-тестов, разбив их на небольшие кусочки – но для множества тестов это полезное умственное упражнение. Спасибо Юстасу Лаужадису за дискуссию по этому поводу.

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

Для простоты предположим, что архитектура этой опции состоит из трех отдельных компонентов:

  • Графический пользовательский интерфейс, отвечающий за то, чтобы пользователь смог подать заявление о займе и увидеть на экране ответ.
  • Слой бизнес-логики Парабанка, отвечающий за сбор входных данных от пользователя, их обогащение информацией об авторизованном пользователе и отправку запроса на займ в обработку займов.
  • Обработка займов, отвечающая за решение, одобрить или отклонить запрос.

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

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

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

 

Шаг 0: пишем E2E-тесты

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

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

Такой тест, созданный с помощью Selenium или Playwright, может выглядеть примерно так:

[TestCase(10000, 1000, 12345, "Approved")]
[TestCase(10000, 100, 12345, "Denied")]
[TestCase(50000, 1000, 12345, "Denied")]
public void ApplyForLoan_CheckResult_ShouldEqualExpectations
(int loanAmount, int downPayment, int fromAccount, string expectedResult)
{
new LoginPage(this.driver)
.LoginAs("john", "demo");
 
new AccountOverviewPage(this.driver)
.SelectMenuItem("Request Loan");
 
var rlp = new RequestLoanPage(this.driver);
 
rlp.SubmitLoanRequest(loanAmount, downPayment, fromAccount);
 
Assert.That(rlp.GetLoanApplicationResult(), Is.EqualTo(expectedResult));
}

Охват этого теста можно представить так:

 

Такие тесты, возможно, кажутся хорошей идеей, но у E2E-тестов есть проблема, с которой вы рано или поздно столкнетесь:

  • Создание таких тестов занимает много времени – код E2E-тестов на самом деле сложнейшая форма автоматизации, и поэтому мне кажется очень странным, что столько людей начинают учиться автоматизировать при помощи инструментов вроде Selenium или Playwright.
  • Выполнение таких тестов занимает много времени – запуск и остановка браузеров, загрузка сайтов, взаимодействие со сторонними системами, все это накапливается.
  • Разбирательство с падениями таких тестов занимает много времени – в нем столько движущихся частей, что зачастую трудно выявить первопричину падения, и эта первопричина зачастую в самом тесте, а не в коде тестируемого приложения.

К тому же создание и прогон этих E2E-тестов обычно осуществляются на очень поздних стадиях цикла разработки, так как требуют доступности всех компонентов и сервисов для тестирования – это противоречит желанию многих команд получать быструю обратную связь о результатах разработки.

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

Шаг 1: отделяем фронтэнд-тесты

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

«Может ли пользователь увидеть результаты заявления на займ в браузере?»

от

«Правильно ли обрабатывается заявление на займ, и соответствует ли результат ожиданиям?»

Делая такое различие, мы получим вот такой охват тестов:

 

У нас есть юнит- и компонентные тесты для фронтэнда, где вызовы API имитируются любой любимой вами тест-библиотекой, а вызовы API, управляющие тестами бэкэнда, можно написать через Postman или библиотеки вроде RestAssured.Net.

Наши исходные E2E-тесты значительно улучшились, но все еще нужно решить ряд проблем:

  • Наши управляемые через API бэкэнд-тесты все еще завязаны на ряд слоев и компонентов.
  • По сравнению с предыдущим E2E-тестом мы утратили тестирование интеграции между фронтэндом (который теперь тестируется изолированно) и бэкэндом ПараБанка.

А значит, снова надо поработать!

Шаг 2: удаляем внешний сервис из уравнения

Еще один шаг в верном направлении. Я видел множество команд, заменяющих реальные внешние системы симуляциями (имитаторами или заглушками). Лично я предпочитаю тестировать реальные вещи, если это возможно, и использую имитаторы в основном для того, чтобы тестировать:

  • Заранее – когда хочется протестировать функциональность, которая в реальности еще находится в разработке.
  • Больше – когда нужно проверить поведение, которое трудно или даже невозможно спровоцировать в реальности по запросу.
  • Чаще – когда настраивать или запускать реальную систему достаточно часто невозможно, хотя это необходимо для тестирования.

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

Замена реального обработчика займов на симуляцию приведет к такому охвату наших тестов:

 

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

Шаг 3: тестирование интеграции между фронтэндом и бэкэндом ПараБанка

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

Это помогает выполнять интеграционное тестирование на более ранних стадиях процесса разработки – по сути мы можем заняться им, пока еще работаем над ПО в локальном окружении. Перефразируя, контрактное тестирование выносит интеграционные тесты вперед, на стадию юнит-тестирования.

В этом случае первым нашим вопросом должен быть «О каком типе контрактного тестирования речь?»  Так как и поставщик, и потребитель – внутренние сервисы ПараБанка, и мы хотим глубоко и детально проверить интеграцию фронтэнда и бэкэнда, то разумно будет выбрать контрактное тестирование, ориентированное на потребителя (CDCT).

Внедрить CDCT нам помогут инструменты вроде Pact или Spring Cloud Contract. После внедрения CDCT для интеграции фронтэнда и бэкэнда охват тестов станет таким:

 

Шаг 4: тестирование игтеграции между бэкэндом и сервисом обработки займов

Затем перейдем к другой интеграции, пострадавшей от разбивки исходного E2E-теста на более мелкие кусочки. Думаю, вашей первой идеей будет применить CDCT и тут, но с этим есть проблемы, затрудняющие реализацию:

  • Этот сервис находится извне ПараБанка, что усложняет взаимодействие и общение команд разработки.
  • Сервис может быть публичным API, открытым для десятков банковских систем – вряд ли команда разработки согласится применить CDCT.

В этом случае двустороннее тестирование контрактов (BDCT) будет более подходящим вариантом. В BDCT и потребитель, и поставщик генерируют свой собственный контракт, а затем их сравнивает независимая третья сторона.

В экосистеме Pact это Pactflow. BDCT облегчает нагрузку на поставщика – все, что нужно сделать, это предоставить спецификацию OpenAPI, а значит, внедрять такую интеграцию легче.

Дополнительный бонус: с шансами мы сократим количество симулируемых на шаге 2 ситуаций. «Счастливые» сценарии, когда обработчик займов отвечает, как ожидается, будут покрыты контрактом – нам придется имитировать только ситуации, когда обработчик ведет себя не покрытым контрактом образом. К примеру, это задержки ответов и серверные ошибки (HTTP 5xx).

Итак, что же мы сделали?

Добавив тесты, проверяющие реализацию бэкэнда, мы получили финальную декомпозицию тестов для заявлений на займ:

 

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

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

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

Обсудить в форуме