Изоляция в тестах с Kafka (остатки)
Все, что не вошло в статью Изоляция в тестах с Kafka
TODO
TODO по коду:
- код проверяет, что одному контейнеру назначена хотя бы одна партиция, а если к контейнеру подключено несколько топиков?
- ожидание завершения состояние спрятать в каптор? будет вызываться каждый раз когда запрашивается getRecords
- getRecords() может вернуться к проверке на количество записей
- обработка сообщений в один поток, а что будет если будет больше? Что кроме повышенной нагрузки?
- вынести в отдельную либу
- в примере мы создаем контейнер для тестового контекса, а если их несколько, то будет и несколько запусков.
- если контекст спринга закеширован, то отваливаются ли консумеры?
- TODO Чистка каптора от сообщений
Добавления
Речь про независимые обработки. Отчеты по клиентам например требует понятного состояния. Нужно обнулять состояние.
Почему не сделать просто большое ожидание перед проверкой? Zero case. Изоляция - мы должны быть точно уверены что все завершилось
Не должно быть такой ситуации, когда в тестовом сценарии есть какой-то вылетевший http запрос или сообщение, обработка которых на момент проверки состояния системы еще не завершена.
Some people prefer to put less emphasis on isolation and more on defining clear dependencies to force tests to run in a specified order. I prefer isolation because it gives you more flexibility in running subsets of tests and parallelizing tests.
Одновременно создается сайд эффект. Asynchronous Behavior. Способы решения: таймер, poll, happens-before лайнарезация,
Так как приложения будет одно на разные тесты, то нам нужно отличать сообщения в рамках теста. Для этого потребуется ключевая информация. Если такой возможности нет, то будет невозможно в массе всех сообщений найти то или иное. (TODO шардирование)?
---- ВЫРЕЗАНО
Пример Хотелось бы чтобы работа в тестах была сталь де приятной Одна из причин почему не любят интеграционные тесты это сложность подготовки среды (много асинхронищины и компонент) Мне хотелось бы предложить / описать в данной статье простое в плане использования решение, но которое вводит необходимость соблюдать требования к коду и дисциплине разработчика.
Лайнаризация обработки в рамках тестового сценария
// TODO напрашивается аналогия с Linearizability, но давай без нее.
- лайнаризация обработки в рамках тестового сценария - по факту это изоляция процессов
Мы выставили лайтест, поэтому нам нужно в точке дождаться распределени консумеров
Полная не возможна, потому что обработка в рамках кафки асинхронна.
Привести пример и показать, что в варианте А на момент наступления события Б обработка завершена или нет, если не завершена, то нужно использовать инструмент принудительного ожидания.
Требования к дисциплине - соблюдение изоляции в тестах.
Иметь возможность следовать аранж-ассерт подходу.
Надежность
Кто то делает слипы или вейты
почему не моки
В целом вопрос сложный. Но ввиду того, что сервисы различаются разнообразием способом реализации, то единственное на что будет надежно завязаться - контракт. А так как контракты сервисов, в которых можно этот подход применять, я не знаю, то значит мы сдвигаемся ближе к публичным контрактам, т.е. к кафке и spring. Как придоставляет свой api, spring IOC инфраструктуру, которую я буду использовать.
Важно заметить
Что предложенный выше вариант не только позволяет удобно и детерминированно тестировать работу с кафкой, но так же предоставляет достаточную изоляцию теста в части кафки.
shared kafka instance
Мы не хотим для каждого теста (группы) поднимать кафку, хотя мы можем это делать быстро и дешево - смотрите статью.
Поэтому нужна изоляция в тестах за счет шардирования через идентификаторы
Если это возможно, если нет, то нужно поднимать инстанс
Поэтому мы не можем воспользоваться spring.kafka.consumer.auto-offset-reset=earliest
.
Видимо поэтому нам не подошел ContainerTestUtils.waitForAssignment()?
spring.kafka.listener.ack-mode=record
spring.kafka.listener.concurrency=1
spring.kafka.listener.poll-timeout=1000
spring.kafka.consumer.auto-offset-reset=latest
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.max-poll-records=10
spring.kafka.producer.acks=1
spring.kafka.producer.retries=5
Что делать, если нет возможности обеспечить изоляцию
Возможно вам стоит рассмотреть вариант перезапу
Если нет возможности обеспечить изоляцию
Это самое идельный вариант. Мы просто проверяем. Проверки на отсутствие выполняются так же как и обычные.
В случае не выполнения условий изоляции (вереный термин?)
То придется встраивать ожидание.
Большой минус - проверки на остутствие записей. Представьте, мы должны проверить, что сообщение НЕ приходит в нужный топик. Если мы напишем getRecords().size() == 0
то в случае с лайнарезированным будет работать. А если без, то это выражение будет справедливо для двух случаев:
- обработки в сервисе еще не завершены
- обработка завершены и ничего не пришло
В данном случае можно воспользоваться таким вариантом .awaitAtMost(100).getRecords().size() == 0
Апи для бакета с эвейтом и без
.await(100).getRecords() .withAwait().getRecords()
.await().getRecords() .getRecords() - тут нет евейта
Статьи по теме
- Eradicating Non-Determinism in Tests
- https://www.smiledigitalhealth.com/our-blog/kafka-integration-testing-partition-assignment - чувак сделал
auto.offset.reset: earliest
- https://habr.com/ru/articles/742786/ - тут чувак написал простые консумеры и протестировал со слипами
- https://www.confluent.io/blog/advanced-testing-techniques-for-spring-kafka/
- https://engineering.atspotify.com/2018/01/testing-of-microservices/ - ! Они выделили Integrated Tests и Integration Tests. Первые это зло, вторые это хорошо.
Статьи где есть картинки на которые можно обратить внимание
Игра “Волк и яйца” Как метафора на event bucket
https://www.confluent.io/blog/advanced-testing-techniques-for-spring-Kafka https://architecturenotes.co/database-sharding-explained/