Переизобретаем БДД тесты

TODO

Повышаем наглядность тестов.

Тест должен не только тестировать но и говорить мне какие запросы куда идут

Contract testing approach to reduce complexity for integration testing with services

Contract testing is a technique for testing an integration point by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a “contract”. - https://docs.pact.io/

Pact

The problem that initiative aims to address revolves around the challenges and intricacies involved in integration testing, especially when dealing with microservices or external APIs. Integration testing, which verifies that different parts of a system work together as intended, can become highly complex and cumbersome when multiple services are involved. The contract testing approach proposes a solution to these challenges by focusing on verifying the interactions between services at the API level based on predefined contracts.

Required to dive into contract testing and say what we can achieve.

Details: Pact, Spring contract

import org.springframework.cloud.contract.spec.Contract; https://docs.pact.io/books https://habr.com/ru/articles/451132/ https://habr.com/ru/companies/testit-tms/articles/570544/ https://github.com/spring-cloud/spring-cloud-contract/blob/main/spring-cloud-contract-stub-runner/src/main/java/org/springframework/cloud/contract/stubrunner/ContractProjectUpdater.java https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/main/producer_java/src/test/java/contracts/beer/intoxication/1_sober.java https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs/blob/sc-contract/contracts/1_shouldAddABook.yml https://softwaremill.com/contract-testing-with-pact/ https://medium.com/@ximna.inc/way-to-microservices-contract-testing-a-spring-pact-implementation-1140aff95d39

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

The focus on the messages (request/response) not the implementation

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

Arrange-Act-Assert снова, визуально выделяем блоки, интуитивно пониманием что все что сверху - arrange

alt text

Кукумбер идея классная, но язык бедный. Дает тоже самое, что drools - заставляет отделить интеграцию от бизнес логики и сосредоточиться на последней.

Цель - написать тест таким образом, чтобы снизить шум от интеграционного кода и выставить вперед бизнес логику

Предложение:

  • spock
  • request captor
  • record captor
  • rest expectation
  • json assert
  • идентификаторы для сценариев

Почему Spock

Почему request captor

Смотрите статью

Почему request captor

Смотрите статью

rest expectation

Обертка над моками для реста.

В статье https://habr.com/ru/articles/781812/ указан способ описание тестов, в итоге приходим к такому

0 def "Forecast for provided city London is 42"() {
1  setup:         
2  def requestCaptor = new RequestCaptor()
3  mockServer.expect(manyTimes(),requestTo("https://weather.com/forecast"))
4          .andExpect(method(HttpMethod.POST))                            
5          .andExpect(requestCaptor)                                      
6          .andRespond(withSuccess('{"result":"42"}',APPLICATION_JSON));  
7  when:         
8  def forecast = weatherService.getForecast("London")
9  then:        
.  forecast == "42"
.  requestCaptor.times == 1           
.  requestCaptor.body.city == "London"
.  requestCaptor.headers.get("Content-Type") == ["application/json"]
}

Строки с 2 по 6 можно заменить на 1: TODO лучше заменить на telegram

0 def "Forecast for provided city London is 42"() {
1  setup:        
2  def requestCaptor = restExpectation.weather.forecast(withSuccess('{"result":"42"}'))
7  when:        
8  def forecast = weatherService.getForecast("London")
9  then:        
.  forecast == "42"
.  requestCaptor.times == 1           
.  requestCaptor.body.city == "London" 
.  requestCaptor.headers.get("Content-Type") == ["application/json"]
}

при этом мы сохраняем структуру запроса и ответа и можем быстро их понять

Почему json assert

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

Пример, когда не очевидно преимущество

requestCaptor.body.chatId == "20000000"
requestCaptor.body.text == "Hi, how can i help you?"
JSONAssert.assertEquals("""{
    "chatId": "20000000",
    "text": "Hi, how can i help you?"
}""", telegramRequestCaptor.bodyString, false)

Пример, когда очевидно преимущество TODO заменить на какой-то реальный пример

requestCaptor.body.update_id == 123123123
requestCaptor.body.message.message_id == "3446"
requestCaptor.body.message.from.id == 5676588
requestCaptor.body.message.from.is_bot == false
requestCaptor.body.message.from.first_name == false
JSONAssert.assertEquals("""{
  "model": "gpt-4",
  "messages": [{
    "role": "user", 
    "content": "Hi!"
  }]
}""", telegramRequestCaptor.bodyString, false)

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

Чего нет в jsonassrt - нельзя верифицировать порядок следования атрибутов. Улучшения для jsonassrt, позволяет добавить обработчики и проверять, например, наличие полей.

Спок уже бдд, добавим сахара 

Вытаскиваем ну ружу то что хотим проверить, поведение и

Не будем ничего изобретать и воспользуемся тем что есть - груви, спок, джсон вссерт

Спрятать подальше особенности языка и фреймворка и принести поближе протокол

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

  • рест экспектецшн

  • Json assert

  • Event bucket

Использование mvc для приемстаенности и сохранени знакомых якорей, чтобы не было шока 

Требователен к тестам Наглядные тесты Пример: jsonassrt, event backet

Чтобы ответить на этот вопрос пришлось написать ряд статей в которых отразить подходы позволяющие писать тесты просто

Сделайте тесты частью свой работы

dsl для тестов

Интеграция и бизнес-логика

Если тестируем первое, то оно должно быть видно, если второе то второе

Не смешиваем интеграционные тесты и тесты бизнес логики

На примере бота для телеги

ideaarticledraft