Каким должен быть охват теста Pact провайдера?

Моя организация начала использовать Pact для создания / проверки контрактов между REST-сервисами / микро-сервисами, написанными на Java около полугода назад. Нам трудно решить, какой должна быть соответствующая сфера охвата или охват теста поставщика, и нам бы понравился некоторый вклад из опыта других пользователей пакта.

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

Мы свелись к двум вариантам:

1. Первый вариант заключается в том, что тест провайдера должен быть строгим контрактным тестом и использовать только класс ресурсов REST сервиса провайдера, высмеивая / заглушая используемые им классы / оркестраторы сервиса и т. Д. Этот контрактный тест будет дополнен тестами компонентов, которые будут проверять детали, заглушенные / проверенные тестом поставщика.

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

Это мысли про для каждого варианта

Профи для варианта 1:

  • Тесты будут проще в реализации и будут занимать меньше места
    => более высокая степень изоляции.
  • В любом случае, возможно, нам понадобятся другие тесты компонентов, чтобы охватить случаи использования, которые обычно не учитываются в ожиданиях потребителя (потоки ошибок и т. Д.). Таким образом, мы не будем смешивать различные виды тестов компонентов (Pact и другие) в одном пакете, что облегчит понимание набора тестов.

Профи для варианта 2:

  • Тесты используют больше "реального" кода => меньший риск ошибок в тестах из-за плохой насмешки.

Мне было бы очень интересно услышать, как обычно тесты вашего провайдера выглядят в этом отношении. Есть ли лучшая практика?

Уточнение того, что мы подразумеваем под "Компонентом". Компонент - это микросервис или модуль в более крупном сервисном приложении. Мы взяли определение "компонента" у Мартина Фаулерса http://martinfowler.com/articles/microservice-testing/.

Сервис / компонент поставщика обычно имеет конечную точку REST в классе ресурсов Джерси. Эта конечная точка является конечной точкой поставщика для теста поставщика Pact. Пример:

@Path("/customer")
public class CustomerResource {

    @Autowired private CustomerOrchestrator customerOrchestrator;

    @GET
    @Path("/{customerId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response get(@PathParam("customerId") String id) {
        CustomerId customerId = CustomerIdValidator.validate(id);
        return Response.ok(toJson(customerOrchestrator.getCustomer(customerId))).build();
    }

В приведенном выше примере @Autowired (мы используем пружину) CustomerOrchestrator может быть либо имитирован при запуске теста провайдера, либо вы можете внедрить настоящий класс "Impl". Если вы решите внедрить настоящий "CustomerOrchestratorImpl.class", он будет иметь дополнительные зависимости bean-компонента @Autowired, которые, в свою очередь, могут иметь другие... и т. Д. Наконец, зависимости окажутся либо в DAO-объекте, который будет выполнять вызов базы данных, либо REST-клиент, который будет выполнять HTTP-вызовы другим нисходящим службам / компонентам.

Если бы мы приняли мое решение "вариант 1" в приведенном выше примере, мы бы смоделировали поле customerOrchestrator в CustomerResource, а если бы мы приняли "вариант 2", мы бы внедрили Impl-классы (реальные классы) для каждой зависимости в CustomerResource. граф зависимостей и создание поддельных записей в базе данных и вместо них поддельных сервисов.

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

Мы создали "тестовую среду", которая автоматически макетирует любую зависимость Autowired, которая явно не объявлена ​​в контексте Spring, так что заглушка / насмешка - это легкий процесс для нас. Это отрывок теста провайдера, который выполняет CustomerResource и инициирует заглушенный компонент CustomerOrchestrator:

@RunWith(PactRunner.class)
@Provider("customer-rest-api")
@PactCachedLoader(CustomerProviderContractTest.class)
public class CustomerProviderContractTest {

    @ClassRule
    public static PactJerseyWebbAppDescriptorRule webAppRule = buildWebAppDescriptorRule();

    @Rule
    public PactJerseyTestRule jersyTestRule = new PactJerseyTestRule(webAppRule.appDescriptor);

    @TestTarget public final Target target = new HttpTarget(jersyTestRule.port);

    private static PactJerseyWebbAppDescriptorRule buildWebAppDescriptorRule() {
        return PactJerseyWebbAppDescriptorRule.Builder.getBuilder()
            .withContextConfigLocation("classpath:applicationContext-test.xml")
            .withRestResourceClazzes(CustomerResource.class)
            .withPackages("api.rest.customer")
            .build();
    }

    @State("that customer with id 1111111 exists")
    public void state1() throws Exception {
        CustomerOrchestrator customerOrchestratorStub = SpringApplicationContext.getBean(CustomerOrchestrator.class)
       when(customerOrchestratorStub.getCustomer(eq("1111111"))).thenReturn(createMockedCustomer("1111111));

    }
    ...

4 ответа

Это вопрос, который часто возникает, и мой ответ - "делай то, что имеет смысл для каждой услуги". Первые микросервисы, для которых использовался pact, были такими маленькими и простыми, что было проще всего протестировать весь сервис без каких-либо насмешек или заглушек. Единственная разница между вызовом реальной службы и вызовом в проверочном тесте заключалась в том, что мы использовали sqlite для тестов. Конечно, мы заглушали звонки в нисходящие сервисы.

Если настроить реальные данные сложнее, чем заглушить, я бы использовал заглушки. Однако! Если вы собираетесь это сделать, то вам необходимо убедиться, что вызовы, которые вы оформили, проверяются так же, как работает пакт. Используйте какое-то общее устройство и убедитесь, что для каждого вызова, который вы заглушаете в тесте поставщика pact, у вас есть соответствующий тест, чтобы убедиться, что поведение соответствует ожидаемому. Как будто вы объединяете тесты совместной работы / контракта вместе, как это: введите описание изображения здесь

Я говорю пойти с вариантом 2.

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

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

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

В конце концов, все зависит от того, как вы используете Pact, если вы используете его для проверки контрактов между потребителем и поставщиком.

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

Спасибо за ваш вклад!

Мы отказались от Пакта.

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

Возвращаясь к вашему вопросу:

Pact — это, по сути, попытка создать общий макет — объявленный запрос и ответ — который заслуживает доверия, поскольку он является общим. Он решает проблему ненадежных макетов, поскольку клиент и служба используют одни и те же объявления. Если вы и дальше высмеиваете свой сервис, чтобы проверить эти макеты, то вы просто обходите стороной суть пакта.

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

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

Итак, я бы сказал, что весь смысл пакта в том, чтобы иметь вариант №2.

Почему мне не нравится пакт

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

Я чувствую, что Pact продает решение, которое большинству не нужно.

Вот как я бы описал вариант использования pact.io (должен соответствовать всем этим)

  • Взаимодействия HTTP Rest имеют решающее значение (игнорируйте события и потоки)

  • Довольно распространенные технологические стеки с хорошей поддержкой языка пактов.

  • Вы не возражаете против специальной спецификации API DSL — игнорируйте jsonschema.org или OAS и т. д.

  • Ваши макеты не заслуживают доверия, если ими не делятся и не проверяют регулярно.

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

  • Коммуникация/координация изменений API затруднена (возможно, даже неэффективна):

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

  • Вы не можете осмысленно проверять свои сборки разработки на экземплярах стадии.

  • Вы не можете создавать общие интеграционные тесты из-за их высокой сложности и длительного времени выполнения сборки/тестирования.

  • Командам стоит потратить огромное количество времени на понимание пакта, его перехватчиков, отслеживания зависимостей пакта, возможности развертывания и проектирования тестов пакта. И стоит продолжать платить эту цену за каждого нового сотрудника, а также за того, чтобы каждый сотрудник каждые 6–12 месяцев тратил дополнительное время на то, чтобы разобраться в скрытых тонкостях договора.

Хорошо, в целом Пакт меня немного отталкивает. Измученный? Я немного настороженно отношусь к любым заявлениям о том, что большинство реализаций технологии XYZ потерпели неудачу, потому что у них нет директивы сверху вниз по использованию этой технологии.

Предложение альтернатив

  • Если вы хотите проверить контракты, изучите проверку контрактов OAS/Swagger или json-schema.org (более стандартный и поддерживаемый DSL) или сторонние решения/сервисы, которые будут имитировать для вас взаимодействие как клиента, так и сервера (даже с записью). /повтор),
  • Посмотрите на «Закон Конвея» и на то, как разделены ваши команды.
  • Если это еще не ясно, я уверен, что большинству организаций, которым нужен договор, действительно нужен...
    • Улучшенные правила управления версиями API,
    • Запланированный срок службы API с обратно совместимыми версиями API.
    • Ворота качества сценической среды или второстепенный этап (пре-продакшн)
    • Коммуникация и понимание зависимостей клиента
    • Отслеживание межкомандных коммуникаций/зависимостей — это проблема, которую руководство не решает, и команды не имеют достаточного количества журналов для отслеживания.
    • Архитектурные проблемы требуют решения (вероятно, распределенный монолит из-за быстрого роста без архитектурного предвидения? Или что-то в этом роде...)
Другие вопросы по тегам