Перейти к основному содержимому

Введение в Spring Cloud Contract

· 4 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Введение

Spring Cloud Contract — это проект, который, проще говоря, помогает нам писать Consumer-Driven Contracts (CDC) .

Это обеспечивает контракт между производителем и потребителем в распределенной системе — как для взаимодействия на основе HTTP, так и на основе сообщений.

В этой быстрой статье мы рассмотрим написание тестовых примеров на стороне производителя и потребителя для Spring Cloud Contract посредством HTTP-взаимодействия.

2. Производитель — серверная часть

Мы собираемся написать CDC на стороне производителя в форме EvenOddController , который просто сообщает, является ли числовой параметр четным или нечетным:

@RestController
public class EvenOddController {

@GetMapping("/validate/prime-number")
public String isNumberPrime(@RequestParam("number") Integer number) {
return Integer.parseInt(number) % 2 == 0 ? "Even" : "Odd";
}
}

2.1. Зависимости Maven

На стороне производителя нам понадобится зависимость spring-cloud-starter-contract-verifier :

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>

И нам нужно настроить spring-cloud-contract-maven-plugin с именем нашего базового тестового класса, который мы опишем в следующем разделе:

<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<extensions>true</extensions>
<configuration>
<baseClassForTests>
com.foreach.spring.cloud.springcloudcontractproducer.BaseTestClass
</baseClassForTests>
</configuration>
</plugin>

2.2. Настройка стороны продюсера

Нам нужно добавить базовый класс в тестовый пакет, который загружает наш контекст Spring:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseTestClass {

@Autowired
private EvenOddController evenOddController;

@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder
= MockMvcBuilders.standaloneSetup(evenOddController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}

В пакет /src/test/resources/contracts/ мы добавим тестовые заглушки , такие как эта в файле shouldReturnEvenWhenRequestParamIsEven.groovy :

import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return even when number input is even"
request{
method GET()
url("/validate/prime-number") {
queryParameters {
parameter("number", "2")
}
}
}
response {
body("Even")
status 200
}
}

Когда мы запускаем сборку, плагин автоматически генерирует тестовый класс с именем ContractVerifierTest , который расширяет наш BaseTestClass и помещает его в /target/generated-test-sources/contracts/ .

Имена тестовых методов происходят от префикса « validate_», связанного с именами наших тестовых заглушек Groovy. Для приведенного выше файла Groovy сгенерированное имя метода будет «validate_shouldReturnEvenWhenRequestParamIsEven» .

Давайте посмотрим на этот автоматически сгенерированный тестовый класс:

public class ContractVerifierTest extends BaseTestClass {

@Test
public void validate_shouldReturnEvenWhenRequestParamIsEven() throws Exception {
// given:
MockMvcRequestSpecification request = given();

// when:
ResponseOptions response = given().spec(request)
.queryParam("number","2")
.get("/validate/prime-number");

// then:
assertThat(response.statusCode()).isEqualTo(200);

// and:
String responseBody = response.getBody().asString();
assertThat(responseBody).isEqualTo("Even");
}

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

Заглушки будут находиться в выходной папке stubs/mapping/ .

3. Потребитель — клиентская сторона

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

Мы добавим BasicMathController, который будет делать HTTP-запрос для получения ответа от сгенерированных заглушек:

@RestController
public class BasicMathController {

@Autowired
private RestTemplate restTemplate;

@GetMapping("/calculate")
public String checkOddAndEven(@RequestParam("number") Integer number) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");

ResponseEntity<String> responseEntity = restTemplate.exchange(
"http://localhost:8090/validate/prime-number?number=" + number,
HttpMethod.GET,
new HttpEntity<>(httpHeaders),
String.class);

return responseEntity.getBody();
}
}

3.1. Зависимости Maven

Для нашего потребителя нам нужно добавить зависимости spring-cloud-contract-wiremock и spring-cloud-contract-stub-runner :

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>

3.2. Настройка стороны потребителя

Теперь пришло время настроить наш обработчик заглушек, который будет информировать нашего потребителя о доступных заглушках в нашем локальном репозитории Maven:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.LOCAL,
ids = "com.foreach.spring.cloud:spring-cloud-contract-producer:+:stubs:8090")
public class BasicMathControllerIntegrationTest {

@Autowired
private MockMvc mockMvc;

@Test
public void given_WhenPassEvenNumberInQueryParam_ThenReturnEven()
throws Exception {

mockMvc.perform(MockMvcRequestBuilders.get("/calculate?number=2")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string("Even"));
}
}

Обратите внимание, что свойство ids аннотации @AutoConfigureStubRunner указывает:

  • com.foreach.spring.cloudgroupId нашего артефакта
  • spring-cloud-contract-producerартефактный идентификатор фляги-заглушки производителя.
  • 8090 — порт, на котором будут запускаться сгенерированные заглушки

4. Когда контракт нарушен

Если мы внесем какие-либо изменения на стороне производителя, которые напрямую влияют на контракт, не обновляя сторону потребителя, это может привести к сбою контракта.

Например, предположим, что мы должны изменить URI запроса EvenOddController на /validate/change/prime-number на стороне производителя.

Если мы не проинформируем нашего потребителя об этом изменении, потребитель все равно отправит свой запрос на URI /validate/prime-number , а тестовые примеры на стороне потребителя выдадут org.springframework.web.client.HttpClientErrorException: 404 Not Found .

5. Резюме

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

И, как всегда, полную реализацию этого туториала можно найти на GitHub .