Microservice wars: JUnit episode V — TestContainers strikes back

Microservice wars: JUnit episode V — TestContainers strikes back

In this talk, we'll explore how infrastructure of back-end integration testing evolves when we switch from monolith to microservices. We'll pay special attention to problems that only appear in testing of microservice applications.

We'll see how JUnit5 can help us make our tests more concise.

We'll test working with databases and message brokers using Docker and test-containers library. If you think that testing asynchronous communication (for example, with RabbitMQ) is hard, then come and see for yourself that it really isn't.

We'll also discuss pitfalls of integration testing in Spring Framework and how to avoid them.

A858338066b0e72ba17cf3823d48bc42?s=128

Korovin Anatoliy

May 18, 2019
Tweet

Transcript

  1. JUnit – Episode 5 TestContainers strikes back

  2. Коровин Анатолий antkorwin antkorwin antkorwin.com t.me/test_tools

  3. None
  4. Monolith PostgreSQL External Camunda BPM https://t.me/test_tools

  5. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  6. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  7. Юнит-тесты Компонент-тесты Микросервис-тесты E2E-тесты @DataJpaTest @WebMvcTest @SpringBootTest // что мы

    хотим тестировать Интеграционные-тесты
  8. Не видно взгляду, что ты тестировать именно хочешь

  9. хранимые процедуры нативные запросы расширения postgres // что мы хотим

    тестировать валидация дата-сетов асинхронные события конвертацию данных
  10. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  11. Monolith PostgreSQL External Camunda BPM // как мы тестировали монолит

  12. External Camunda BPM Monolith PostgreSQL // тестируем работу с БД

  13. Task C UUID id; String name; int estimate; // тестируем

    работу с БД
  14. Task C UUID id; String name; int estimate; TaskRepository I

    @Query(“select..”) List<Task>findAll(..) // тестируем работу с БД
  15. Task C UUID id; String name; int estimate; TaskRepository I

    @Query(“select..”) List<Task>findAll(..) TaskRepositoryTest C void testFindAll(){ ..! } // тестируем работу с БД
  16. BasePostgresTest A TaskRepositoryTest C void testFindAll(){ ..! } C EpicRepositoryTest

    C void createEpic(){ ..! } // выносим конфигурацию в базовый класс C MilestoneRepositoryTest C void getMilestone(){ ..! }
  17. BasePostgresTest A TaskRepositoryTest C void testFindAll(){ ..! } C EpicRepositoryTest

    C void createEpic(){ ..! } C MilestoneRepositoryTest C void getMilestone(){ ..! } ? // а на какой базе тестируем?
  18. BasePostgresTest A TaskRepositoryTest C void testFindAll(){ ..! } C EpicRepositoryTest

    C void createEpic(){ ..! } C MilestoneRepositoryTest C void getMilestone(){ ..! } H2 // а на какой базе тестируем?
  19. // тестировать работу с базой на H2

  20. PostgreSQL BasePostgresTest A TaskRepositoryTest C void testFindAll(){ ..! } AnyRepositoryTest

    C void testAny(){ ..! } AnyRepositoryTest C void testAny(){ ..! } TaskServiceTest C void testGetTask(){ ..! }
  21. PostgreSQL BasePostgresTest A TaskRepositoryTest C void testFindAll(){ ..! } AnyRepositoryTest

    C void testAny(){ ..! } AnyRepositoryTest C void testAny(){ ..! } TaskServiceTest C void testGetTask(){ ..! } // заглянем // внутрь // тестов
  22. public class TaskServiceTest extends BasePostgresTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } }
  23. public class TaskServiceTest extends BasePostgresTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } }
  24. public class TaskServiceTest extends BasePostgresTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } }
  25. public class TaskServiceTest extends BasePostgresTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } }
  26. public class TaskServiceTest extends BasePostgresTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } } // а что внутри BasePostgresTest?
  27. @RunWith(SpringRunner.class) @DataJpaTest public abstract class BasePostgresTest { @Rule public DBUnitRule

    dbUnitRule = DBUnitRule.instance(() -> { ... }); @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer(); }
  28. @RunWith(SpringRunner.class) @DataJpaTest public abstract class BasePostgresTest { @Rule public DBUnitRule

    dbUnitRule = DBUnitRule.instance(() -> { ... }); @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer(); }
  29. @RunWith(SpringRunner.class) @DataJpaTest public abstract class BasePostgresTest { @Rule public DBUnitRule

    dbUnitRule = DBUnitRule.instance(() -> { ... }); @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer(); }
  30. External Camunda BPM Monolith PostgreSQL BasePostgresTest A // тестируем работу

    с БД
  31. Monolith PostgreSQL External Camunda BPM BasePostgresTest A // тестирование монолита

  32. PostgreSQL Camunda BPM BasePostgresTest A External Monolith // тестируем отправку

    сообщений
  33. BaseRabbitMqTest A SendFirstEventTest C void testSend(){ ..! } EventListenerTest C

    void testReceive(){ ..! }
  34. PostgreSQL Camunda BPM BasePostgresTest A External Monolith BaseRabbitMqTest A //

    тестируем отправку сообщений
  35. Monolith PostgreSQL External Camunda BPM // тестирование монолита BasePostgresTest A

    BaseRabbitMqTest A
  36. Monolith PostgreSQL External Camunda BPM // тестирование // монолита BasePostgresTest

    A BaseRabbitMqTest A BaseElasticTest A BaseBpmnTest A BaseMongoTest A
  37. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  38. // распиливаем // монолит

  39. Document Service Complaint Service Search Service PostgreSQL MongoDB Elastic Search

    HR Service PostgreSQL REST REST Auth Service PostgreSQL // распиливаем // монолит
  40. Document Service Complaint Service Search Service PostgreSQL MongoDB Elastic Search

    REST REST Auth Service PostgreSQL HR Service PostgreSQL BasePostgresTest A BaseMvcTest A // тестируем
  41. Complaint Service Search Service PostgreSQL Elastic Search HR Service PostgreSQL

    REST REST Auth Service PostgreSQL BaseMongoTest A BaseMvcTest A Document Service MongoDB // тестируем
  42. Document Service Search Service PostgreSQL MongoDB Elastic Search HR Service

    PostgreSQL REST REST Auth Service BasePostgresTest A BaseMvcTest A Complaint Service PostgreSQL BaseRabbitMqTest A // тестируем
  43. Document Service Complaint Service PostgreSQL MongoDB HR Service PostgreSQL REST

    REST Auth Service PostgreSQL BaseElasticTest A BaseMvcTest A BaseRabbitMqTest A Search Service Elastic Search // тестируем
  44. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  45. BasePostgresTest A BaseMvcTest A Auth Service BaseMongoTest A BaseMvcTest A

    Document Service Complaint Service BaseRabbitMqTest A BaseMvcTest A BasePostgresTest A Search Service BaseElasticTest A BaseMvcTest A BaseRabbitMqTest A
  46. BasePostgresTest A BaseMvcTest A Auth Service BaseMongoTest A Document Service

    Complaint Service BaseRabbitMqTest A BasePostgresTest A Search Service BaseElasticTest A BaseRabbitMqTest A BaseMvcTest A BaseMvcTest A BaseMvcTest A
  47. BasePostgresTest A BaseMvcTest A Auth Service BaseMongoTest A Document Service

    Complaint Service BaseRabbitMqTest A Search Service BaseElasticTest A BaseRabbitMqTest A BaseMvcTest A BaseMvcTest A BaseMvcTest A BasePostgresTest A
  48. BasePostgresTest A BaseMvcTest A Auth Service BaseMongoTest A Document Service

    Complaint Service BaseRabbitMqTest A Search Service BaseElasticTest A BaseMvcTest A BaseMvcTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A
  49. Document Service Complaint Service Search Service HR Service Template Service

    Message Service Timer Service // сервисов // становится // больше
  50. Document Service Complaint Service Search Service HR Service Template Service

    Message Service Timer Service // сервисов // становится // больше Auth Service Notify Service Delivery Service Point Service Delivery Favorite Comments Service Poll Service Wiki Service Subscribe Service Report Service
  51. Auth Service Document Service Complaint Service Search Service HR Service

    Template Service Message Service Notify Service Report Service Delivery Service Point Service BPMN Service Chat Service Calendar Service Timer Service Link Service Folder Service Delivery Gate Service Content Service Favorite Static Service Group Service Map Service Comments Service Poll Service Wiki Service Sign Subscribe Service CaseFile Service Config Service // сервисов // становится // больше
  52. // Продолжать копировать базовые классы?

  53. Auth Service Document Service Complaint Service Search Service // как

    можно решить эту проблему? BasePostgresTest A BaseMvcTest A BaseMongoTest A BaseMvcTest A BaseRabbitMqTest A BaseMvcTest A BasePostgresTest A BaseElasticTest A BaseMvcTest A BaseRabbitMqTest A
  54. abstract-tests.jar // вынесем в отдельную зависимость Auth Service Document Service

    Complaint Service Search Service BaseMongoTest A BaseElasticTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseActiveMqTest A
  55. abstract-tests.jar // всегда ли нужны все конфигурации Document Service BaseMongoTest

    A BaseElasticTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseActiveMqTest A
  56. spring-boot-starter-activemq spring-boot-starter-data-jpa spring-boot-starter-web spring-boot-starter-data-mongodb org.postgresql org.testcontainers BaseMongoTest A BaseElasticTest A

    BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseActiveMqTest A // много лишних зависимостей Document Service abstract-tests.jar
  57. spring-boot-starter-data-jpa spring-boot-starter-web org.postgresql org.testcontainers org.testcontainers spring-boot-starter- data-mongodb pg-tests.jar mvc-tests.jar mongo-tests.jar

    BasePostgresTest A BaseMvcTest A Data Service Web Service Document Service BaseMongoTest A // разделим по логике
  58. mvc-test.jar postgres-test.jar // А что, если нужно и то и

    другое? BaseMvcTest A BasePostgresTest A TaskControllerTest C void create() {..} void get() {..} void delete() {..}
  59. Еще один абстрактный класс // как бы мы сделали это

    в монолите PostgreSQL MVC Context Сonfig BasePostgresMvcTest A TaskControllerTest C void create() {..} void get() {..} void delete() {..}
  60. // и так придется делать для каждой комбинации BaseMvcTest A

    BasePostgresTest A
  61. // и так придется делать для каждой комбинации BaseMvcTest A

    BasePostgresTest A BaseRabbitMqTest A BaseElasticTest A BaseMongoTest A BaseMvcTest A
  62. // и так придется делать для каждой комбинации BaseMvcTest A

    BasePostgresTest A BaseRabbitMqTest A BaseElasticTest A BaseMongoTest A BaseElasticTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseMongoTest A BaseMvcTest A BaseElasticTest A BaseRabbitMqTest A BaseMongoTest A BaseMvcTest A BaseElasticTest A BaseMongoTest A BaseMvcTest A
  63. // и так придется делать для каждой комбинации BaseMvcTest A

    BasePostgresTest A BaseRabbitMqTest A BaseElasticTest A BaseMongoTest A BaseElasticTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseMongoTest A BaseMvcTest A BaseElasticTest A BaseRabbitMqTest A BaseMongoTest A BaseMvcTest A BaseElasticTest A BaseMongoTest A BaseMvcTest A BaseRabbitMqTest A BaseElasticTest A BaseElasticTest A BasePostgresTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseMongoTest A BaseMongoTest A BaseMvcTest A
  64. Не – не – не, моя умея только копироватя, Сопровождать

    моя не училса
  65. Еще один абстрактный класс // как бы мы сделали это

    в монолите PostgreSQL MVC Context Сonfig BasePostgresMvcTest A TaskControllerTest C void create() {..} void get() {..} void delete() {..}
  66. mvc-test.jar postgres-test.jar // А что в микросервисах? BasePostgresMvcTest A TaskControllerTest

    C void create() {..} void get() {..} void delete() {..} PostgreSQL MVC Context Сonfig
  67. mvc-test.jar postgres-test.jar // А что в микросервисах? BasePostgresMvcTest A TaskControllerTest

    C void create() {..} void get() {..} void delete() {..} PostgreSQL MVC Context Сonfig WARNING! DO NOT INHERIT TESTS!
  68. public class TaskServiceTest extends BasePostgresMvcTest { @Test @DataSet(value = "datasets/task.json”)

    public void getTask() { Task task = taskService.get(TASK_ID); assertThat(task).isNotNull(); } }
  69. @EnableMvcTest @EnablePostgresTest public class TaskServiceTest { @Test public void getTask()

    { ... } } // заменим наследование мета-аннотациями
  70. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @RunWith(SpringRunner.class) @AutoConfigureMockMvc public @interface EnableMvcTests { } //

    что внутри @EnableMvcTests?
  71. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) public @interface EnablePostgresTests

    { } // что внутри @EnablePostgresTests?
  72. 1: @Retention(RetentionPolicy.RUNTIME) 2: @Target(ElementType.TYPE) 3: @RunWith(SpringRunner.class) 4: @DataJpaTest 5: @AutoConfigureTestDatabase(replace

    = NONE) 6: public @interface EnablePostgresTests { 7: 8: } // Не все так просто
  73. 1: @Retention(RetentionPolicy.RUNTIME) 2: @Target(ElementType.TYPE) 3: @RunWith(SpringRunner.class) 4: @DataJpaTest 5: @AutoConfigureTestDatabase(replace

    = NONE) 6: public @interface EnablePostgresTests { 7: 8: } // Не все так просто
  74. JUnit4 @RunWith •  class level •  non repeatable

  75. // Решение

  76. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ExtendWith(SpringExtension.class) @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) public @interface EnableDataTests

    { } Repeatable & Inherited // Мета-аннотации и ExtendWith
  77. @EnablePostgresTest class TaskServiceTest { @Test void getTask() { ... }

    } // Компонуем, как хотим
  78. @EnableMvcTest @EnablePostgresTest class TaskServiceTest { @Test void getTask() { ...

    } } // Компонуем, как хотим
  79. @EnableMvcTest @EnablePostgresTest @EnableRabbitMqTest class TaskServiceTest { @Test void getTask() {

    ... } } // Компонуем, как хотим
  80. @EnableMongoDbTest class DocumentServiceTest { @Test void get() { ... }

    } // Компонуем, как хотим
  81. @EnableMvcTest @EnableMongoDbTest class DocumentServiceTest { @Test void get() { ...

    } } // Компонуем, как хотим
  82. @EnableMvcTest @EnableMongoDbTest @EnableActiveMqTest class DocumentServiceTest { @Test void get() {

    ... } } // Компонуем, как хотим
  83. хранимые процедуры нативные запросы расширения postgres // тестируем

  84. PostgresTestContainersExtension C EnablePostgresTestContainers @ PostgreSQL

  85. PostgresTestContainersExtension C EnablePostgresTestContainers @ PostgreSQL EnablePostgresIntegrationTest @ EnablePostgresDataTest @

  86. @EnablePostgresDataTest! class NativeFunctionTest {! ! @PersistenceContext private EntityManager entityManager;! !

    @Test! @Sql("/stored_functions/magic_func.sql")! void testStoredFunc() {! StoredProcedureQuery query = entityManager.createStoredProcedureQuery(”magic");! ! query.execute(); List resultList = query.getResultList(); ! int result = (int) resultList.get(0);! Assertions.assertThat(result).isEqualTo(1234);! }! }
  87. @EnablePostgresDataTest! class NativeFunctionTest {! ! @PersistenceContext private EntityManager entityManager;! !

    @Test! @Sql("/stored_functions/magic_func.sql")! void testStoredFunc() {! StoredProcedureQuery query = entityManager.createStoredProcedureQuery(”magic");! ! query.execute(); List resultList = query.getResultList(); ! int result = (int) resultList.get(0);! Assertions.assertThat(result).isEqualTo(1234);! }! }
  88. @EnablePostgresDataTest! class NativeFunctionTest {! ! @PersistenceContext private EntityManager entityManager;! !

    @Test! @Sql("/stored_functions/magic_func.sql")! void testStoredFunc() {! StoredProcedureQuery query = entityManager.createStoredProcedureQuery(”magic");! ! query.execute(); List resultList = query.getResultList(); ! int result = (int) resultList.get(0);! Assertions.assertThat(result).isEqualTo(1234);! }! }
  89. None
  90. @EnablePostgresDataTest! class NativeFunctionTest {! ! @PersistenceContext private EntityManager entityManager;! !

    @Test! @Sql("/stored_functions/magic_func.sql")! void testStoredFunc() {! StoredProcedureQuery query = entityManager.createStoredProcedureQuery(”magic");! ! query.execute(); List resultList = query.getResultList(); ! int result = (int) resultList.get(0);! Assertions.assertThat(result).isEqualTo(1234);! }! }
  91. @EnablePostgresDataTest! class NativeFunctionTest {! ! @PersistenceContext private EntityManager entityManager;! !

    @Test! @Sql("/stored_functions/magic_func.sql")! void testStoredFunc() {! StoredProcedureQuery query = entityManager.createStoredProcedureQuery(”magic");! ! query.execute(); List resultList = query.getResultList(); ! int result = (int) resultList.get(0);! Assertions.assertThat(result).isEqualTo(1234);! }! }
  92. // тестируем валидация дата-сетов агрегации в mongodb

  93. // Тоже самое для MongoDB MongoDbTestContainersExtension C EnableMongoDbTestContainers @

  94. // Тоже самое для MongoDB MongoDbTestContainersExtension C MongoDbExtension C ExpectedMongoDataSet

    @ ExpectedMongoDataSet @ EnableMongoDbTestContainers @
  95. @MongoDbIntegrationTest class MongoTest { @Autowired private MongoTemplate mongoTemplate; @Test @MongoDataSet(value

    = "/dataset/bar_dataset.json") void testFindById() { Bar bar = mongoTemplate.findById("55ffed", Bar.class); Assertions.assertThat(bar) .isNotNull() .extracting(Bar::getData) .containsOnly(“A New Hope"); } }
  96. @MongoDbIntegrationTest class MongoTest { @Autowired private MongoTemplate mongoTemplate; @Test @MongoDataSet(value

    = "/dataset/bar_dataset.json") void testFindById() { Bar bar = mongoTemplate.findById("55ffed", Bar.class); Assertions.assertThat(bar) .isNotNull() .extracting(Bar::getData) .containsOnly(“A New Hope"); } }
  97. @MongoDbIntegrationTest class MongoTest { @Autowired private MongoTemplate mongoTemplate; @Test @MongoDataSet(value

    = "/dataset/bar_dataset.json") void testFindById() { Bar bar = mongoTemplate.findById("55ffed", Bar.class); Assertions.assertThat(bar) .isNotNull() .extracting(Bar::getData) .containsOnly(“A New Hope"); } }
  98. { "com.antkorwin.springtestmongo.Bar": [ { "id” : "18300a", "data” : "Wild

    Coyote" }, { "id” : "55ffed", "data” : "A New Hope" } ] }
  99. @MongoDbIntegrationTest class MongoTest { @Autowired private MongoTemplate mongoTemplate; @Test @MongoDataSet(value

    = "/dataset/bar_dataset.json") void testFindById() { Bar bar = mongoTemplate.findById("55ffed", Bar.class); Assertions.assertThat(bar) .isNotNull() .extracting(Bar::getData) .containsOnly(“A New Hope"); } }
  100. @MongoDbIntegrationTest class MongoTest { @Autowired private MongoTemplate mongoTemplate; @Test @MongoDataSet(value

    = "/dataset/bar_dataset.json") void testFindById() { Bar bar = mongoTemplate.findById("55ffed", Bar.class); Assertions.assertThat(bar) .isNotNull() .extracting(Bar::getData) .containsOnly(“A New Hope"); } }
  101. @Test @ExpectedMongoDataSet("dataset/expected_tasks.json") void dateTimeNow() { Task firstTask = Task.builder() .title("Turn

    to the dark side of the Force") .dueDate(new Date()) .build(); Task secondTask = Task.builder() .title("Defeat the resistance forces") .dueDate(tomorrow()) .build(); mongoTemplate.save(firstTask); mongoTemplate.save(secondTask); }
  102. @Test @ExpectedMongoDataSet("dataset/expected_tasks.json") void dateTimeNow() { Task firstTask = Task.builder() .title("Turn

    to the dark side of the Force") .dueDate(new Date()) .build(); Task secondTask = Task.builder() .title("Defeat the resistance forces") .dueDate(tomorrow()) .build(); mongoTemplate.save(firstTask); mongoTemplate.save(secondTask); }
  103. @Test @ExpectedMongoDataSet("dataset/expected_tasks.json") void dateTimeNow() { Task firstTask = Task.builder() .title("Turn

    to the dark side of the Force") .dueDate(new Date()) .build(); Task secondTask = Task.builder() .title("Defeat the resistance forces") .dueDate(tomorrow()) .build(); mongoTemplate.save(firstTask); mongoTemplate.save(secondTask); }
  104. @Test @ExpectedMongoDataSet("dataset/expected_tasks.json") void dateTimeNow() { Task firstTask = Task.builder() .title("Turn

    to the dark side of the Force") .dueDate(new Date()) .build(); Task secondTask = Task.builder() .title("Defeat the resistance forces") .dueDate(tomorrow()) .build(); mongoTemplate.save(firstTask); mongoTemplate.save(secondTask); }
  105. @Test @ExpectedMongoDataSet("dataset/expected_tasks.json") void dateTimeNow() { Task firstTask = Task.builder() .title("Turn

    to the dark side of the Force") .dueDate(new Date()) .build(); Task secondTask = Task.builder() .title("Defeat the resistance forces") .dueDate(tomorrow()) .build(); mongoTemplate.save(firstTask); mongoTemplate.save(secondTask); }
  106. { "com.antkorwin.starwars.mongotest.Task": [ { "dueDate” : "date-match:[NOW]", "title" : "Turn

    to the dark side of the Force" }, { ”dueDate” : "date-match:[NOW]+1(DAYS)", "title” : "Defeat the resistance forces" } ] }
  107. { "com.antkorwin.starwars.mongotest.Task": [ { "dueDate” : "date-match:[NOW]", "title" : "Turn

    to the dark side of the Force" }, { ”dueDate” : "date-match:[NOW]+1(DAYS)", "title” : "Defeat the resistance forces" } ] }
  108. // тестируем валидация дата-сетов конвертацию данных

  109. RabbitMqTestContainersExtension C EnableRabbitMqTestContainers @ RabbitMqMessagesExtension C ExpectedMessages @

  110. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  111. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  112. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  113. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  114. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  115. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  116. { "com.antkorwin.starwars.rabbitmq.Dreadnought": [ { "stormtroopers” : "20000" } ], "com.antkorwin.starwars.rabbitmq.XWing":[

    { "name” : "Alpha", "stormtroopers” : 3 }, { "name” : "Bravo", "stormtroopers” : 4 } ] }
  117. @EnableRabbitMqTest class StarWarsAsyncTest{ @Autowired private AmqpTemplate amqpTemplate; @Test @ExpectedMessages(queue =

    ”build-queue", messagesFile = “expected_messages.json") void testSendListOfMessages() { amqpTemplate.convertAndSend("build-queue", new Dreadnought(20000)); amqpTemplate.convertAndSend("build-queue", new XWing("Alpha", 3)); amqpTemplate.convertAndSend("build-queue", new XWing("Bravo", 4)); }
  118. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  119. TestContainers!

  120. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ExtendWith(PostgresTcExtension.class) public @interface EnablePostgresTestContainers { }

  121. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } }
  122. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } }
  123. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } }
  124. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } } // что не так?
  125. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } } // зачем так?
  126. public class PostgresTcExtension implements Extension { static { PostgreSQLContainer postgres

    = new PostgreSQLContainer(); postgres.start(); System.setProperty(”spring.datasource.driver-class-name”, postgres.getDriverClassName()); System.setProperty(”spring.datasource.url”, postgres.getJdbcUrl()); System.setProperty(”spring.datasource.username”, postgres.getUsername()); System.setProperty(”spring.datasource.password”, postgres.getPassword()); } } // зачем так? хотим один контейнер , на прогон всех тестов,
  127. Поищем в JUnit5 ? Нужен BeforeSuite callback !

  128. invokeBeforeEachCallbacks(context); if (throwableCollector.isEmpty()) { invokeBeforeEachMethods(context); if (throwableCollector.isEmpty()) { invokeBeforeTestExecutionCallbacks(context); if

    (throwableCollector.isEmpty()) { invokeTestMethod(context, dynamicTestExecutor); } invokeAfterTestExecutionCallbacks(context); } invokeAfterEachMethods(context); } invokeAfterEachCallbacks(context); // нужен BeforeSuite callback
  129. Возможно, скоро будет…

  130. Сделай cвой Extension

  131. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  132. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  133. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  134. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  135. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  136. public class PostgresTestExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext

    context) throws Exception { if (containerAlreadyStarted(context)) return; if (!findTag(getTestDescriptor(context), TC_TAG)) return; startContainer(); markAsStarted(context); } }
  137. private void markAsStarted(ExtensionContext context) {, , context.getRoot(), .getStore(NAMESPACE), .put(TESTCONTAINERS_PG_TAG, true);,

    }, Шарим данные между: -  тестовыми методами -  тестовыми классами // используем ExtensionContext
  138. А удалять контейнеры кто будет?

  139. // а как подключить Extension ко всем тестам? ATest BTest

    CTest BeforeSuite Extension
  140. /resources/META-INF/services/ org.junit.jupiter.api.extension.Extension,,, com.....PostgresTestExtension , , , /resources/, junit-platform.properties, , junit.jupiter.extensions.autodetection.enabled

    = true , // делаем глобальный Extension
  141. static init. block custom before suite

  142. static init. block custom before suite less clean code more

    clean code
  143. static init. block custom before suite junit4 и junit5 less

    clean code more clean code только junit5
  144. static init. block custom before suite junit4 и junit5 less

    clean code прост в написании more clean code только junit5 сложная конструкция
  145. static init. block custom before suite junit4 и junit5 less

    clean code прост в написании легко компоновать junit-platform.properties, more clean code только junit5 сложная конструкция переопределение
  146. DEMO

  147. // Выводы С JUnit5 можно уменьшить дублирование кода в микросервисных

    проектах Мета-аннотации помогают собрать нужную конфигурацию теста, как из конструктора Вместе с TestContainers можно использовать production-окружение в тестах
  148. // Выводы С JUnit5 можно уменьшить дублирование кода в микросервисных

    проектах Мета-аннотации помогают собрать нужную конфигурацию теста, как из конструктора Вместе с TestContainers можно использовать production-окружение в тестах
  149. // Выводы С JUnit5 можно уменьшить дублирование кода в микросервисных

    проектах Мета-аннотации помогают собрать нужную конфигурацию теста, как из конструктора Вместе с TestContainers можно использовать production-окружение в тестах
  150. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  151. Работает: Не работает: CTest ATest BTest ATest BTest CTest

  152. Темная сторона Context Caching,

  153. KEY CONTEXT EMPTY_CACHE ATest BTest CTest

  154. KEY CONTEXT EMPTY_CACHE @SpringBootTest @ExtendWith(SpringExtension.class) class ATest { @Autowired private

    TemplateService templateService; @Test void build() { String result = templateService.build(“%d + %d = %d”, 1, 2, 3); assertThat(result).isEqualTo(“1 + 2 = 3”); } ATest BTest CTest
  155. ATest BTest CTest @SpringBootTest @ExtendWith(SpringExtension.class) class ATest { @Autowired private

    TemplateService templateService; @Test void build() { String result = templateService.build(“%d + %d = %d”, 1, 2, 3); assertThat(result).isEqualTo(“1 + 2 = 3”); } put(key, context)
  156. @SpringBootTest @ExtendWith(SpringExtension.class) class ATest { @Autowired private TemplateService templateService; @Test

    void build() { String result = templateService.build(“%d + %d = %d”, 1, 2, 3); assertThat(result).isEqualTo(“1 + 2 = 3”); } ATest BTest CTest
  157. @DataJpaTest @ExtendWith(SpringExtension.class) class BTest { @Test void createTest() { ...

    } } get(key) ATest BTest CTest
  158. put(key, context) @DataJpaTest @ExtendWith(SpringExtension.class) class BTest { @Test void createTest()

    { ... } } ATest BTest CTest
  159. @DataJpaTest @ExtendWith(SpringExtension.class) class BTest { @Test void createTest() { ...

    } } ATest BTest CTest
  160. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Autowired private DeliveryService deliveryService;

    @Test @ExpectedMessage(queue = ”test-queue", message = "secret text") void sendMessageTest() { deliveryService.sendMessage("secret text"); } } ATest BTest CTest
  161. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Autowired private DeliveryService deliveryService;

    @Test @ExpectedMessage(queue = ”test-queue", message = "secret text”) void sendMessageTest() { deliveryService.sendMessage("secret text"); } } get(key) ATest BTest CTest
  162. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Autowired private DeliveryService deliveryService;

    @Test @ExpectedMessage(queue = ”test-queue", message = "secret text”) void sendMessageTest() { deliveryService.sendMessage("secret text"); } } ATest BTest CTest
  163. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Autowired private DeliveryService deliveryService;

    @Test @ExpectedMessage(queue = "test-queue", message = "secret text") void sendMessageTest() { deliveryService.sendMessage("secret text"); } } ATest BTest CTest
  164. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { ...

  165. @SpringBootTest @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { ...

  166. @Retention(RetentionPolicy.RUNTIME)! @Target(ElementType.TYPE)! @ExtendWith(RabbitMqTcExtension.class)! public @interface EnableRabbitMqTestContainers {! } !

  167. @Retention(RetentionPolicy.RUNTIME)! @Target(ElementType.TYPE)! @ExtendWith(RabbitMqTcExtension.class)! public @interface EnableRabbitMqTestContainers {! } !

  168. None
  169. None
  170. None
  171. Как работает Spring и JUnit

  172. Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod

  173. RabbitMqExtension newInstance SpringExtension Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  174. RabbitMqExtension newInstance SpringExtension static { ... } <clinit> Create Extensions

    BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  175. RabbitMqExtension newInstance SpringExtension static { ... } <clinit> Create Extensions

    BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  176. SpringExtension BeforeAll Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke

    TestMethod
  177. SpringExtension TestContextManager BeforeAll Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  178. SpringExtension TestContextManager TestExecutionListener BeforeAll foreach: Create Extensions BeforeAll PostProcess TestInstance

    Load Context Invoke TestMethod
  179. SpringExtension TestContextManager TestExecutionListener foreach: BeforeAll DirtiesContextBeforeModesTestExecutionListener Create Extensions BeforeAll PostProcess

    TestInstance Load Context Invoke TestMethod beforeTestClass:
  180. SpringExtension postProcess TestInstance Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  181. SpringExtension postProcess TestInstance TestContextManager Create Extensions BeforeAll PostProcess TestInstance Load

    Context Invoke TestMethod
  182. SpringExtension TestExecutionListener postProcess TestInstance TestContextManager Create Extensions BeforeAll PostProcess TestInstance

    Load Context Invoke TestMethod foreach:
  183. Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod SpringExtension

    TestExecutionListener DependencyInjectionTestExecutionListener postProcess TestInstance TestContextManager prepareTestInstance:
  184. DependencyInjectionTestExecutionListener Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod

  185. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  186. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod
  187. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod
  188. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod FileProperties
  189. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod FileProperties SystemProperties
  190. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod FileProperties SystemProperties InlinedProperties
  191. SpringBootContextLoader DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext loadContext SystemProperties FileProperties InlinedProperties ActiveProfiles ConfigClasses

    Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  192. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext loadContext SpringBootContextLoader SpringApplication run Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod
  193. SpringApplication Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod

  194. SpringApplication prepareEnvironment Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke

    TestMethod
  195. SpringApplication prepareEnvironment ConfigFileApplicationListener ApplicationEnvironmentPreparedEvent EPP 1 EPP 2 EPP 3

    Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  196. // The Force here:

  197. SpringApplication prepareEnvironment ConfigFileApplicationListener ApplicationEnvironmentPreparedEvent EPP 1 EPP 2 EPP 3

    Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  198. SpringApplication prepareEnvironment prepareContext Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  199. SpringApplication prepareEnvironment Magic!!! prepareContext Create Extensions BeforeAll PostProcess TestInstance Load

    Context Invoke TestMethod
  200. SpringApplication prepareEnvironment prepareContext Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  201. SpringApplication prepareEnvironment prepareContext Create Extensions BeforeAll PostProcess TestInstance Load Context

    Invoke TestMethod
  202. SpringApplication prepareEnvironment prepareContext ApplicationContextInitializer foreach: Create Extensions BeforeAll PostProcess TestInstance

    Load Context Invoke TestMethod
  203. SpringApplication prepareEnvironment prepareContext refreshContext Create Extensions BeforeAll PostProcess TestInstance Load

    Context Invoke TestMethod
  204. SpringApplication prepareEnvironment prepareContext refreshContext BeanFactories Create Extensions BeforeAll PostProcess TestInstance

    Load Context Invoke TestMethod
  205. SpringApplication prepareEnvironment prepareContext refreshContext BeanFactories BeanPostProcessors foreach: Create Extensions BeforeAll

    PostProcess TestInstance Load Context Invoke TestMethod
  206. SpringApplication prepareEnvironment prepareContext refreshContext BeanFactories BeanPostProcessors ConfigurationPropertiesBindingPostProcessor foreach: Create Extensions

    BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  207. Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod SpringApplication

    prepareEnvironment prepareContext refreshContext BeanFactories BeanPostProcessors ConfigurationPropertiesBindingPostProcessor foreach:
  208. SpringApplication prepareEnvironment prepareContext refreshContext Create Extensions BeforeAll PostProcess TestInstance Load

    Context Invoke TestMethod
  209. DependencyInjectionTestExecutionListener DefaultCacheAwareContextLoaderDelegate loadContext ContextCache get loadContext SpringBootContextLoader put Create Extensions

    BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  210. Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod @SpringBootTest

    @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Test void sendMessageTest() { ... } }
  211. container.start(); ! setProperty(”host”, …);! setProperty(”port”, …);

  212. Load Context EMPTY CACHE container.start(); ! setProperty(”host”, …);! setProperty(”port”, …);

  213. Load Context Prepare Environment EMPTY CACHE container.start(); ! setProperty(”host”, …);!

    setProperty(”port”, …);
  214. Load Context Prepare Environment Test Run EMPTY CACHE container.start(); !

    setProperty(”host”, …);! setProperty(”port”, …);
  215. container.start(); ! setProperty(”host”, …);! setProperty(”port”, …); Load Context Prepare Environment

    Test Run LOAD FROM CACHE EMPTY CACHE
  216. None
  217. None
  218. None
  219. // Что делать? DarthVaderTest

  220. JediTest YodaTest AnakinSkywalkerTest JarJarBinksTest BobaFettTest ObiWanKenobiTest DartMaulTest LukeSkywalkerTest DarthVaderTest //

    Что делать?
  221. JediTest YodaTest AnakinSkywalkerTest JarJarBinksTest BobaFettTest ObiWanKenobiTest DartMaulTest LukeSkywalkerTest DarthVaderTest //

    Что делать?
  222. JediTest YodaTest AnakinSkywalkerTest JarJarBinksTest BobaFettTest ObiWanKenobiTest DartMaulTest LukeSkywalkerTest DarthVaderTest //

    Что делать? Turn to the dark side Context mutation
  223. 100 10 интеграционных тестов уникальных контекстов // Может без КЭША

    обойдемся? ~12 sec старт нового контекста 2 min 20 min с кэшем без кэша
  224. // Выводы С JUnit5 можно уменьшить дублирование кода в микросервисных

    проектах Мета-аннотации помогают собрать нужную конфигурацию теста, как из конструктора Вместе с TestContainers можно использовать production-окружение в тестах Spring Context Caching – сложный механизм, нужно понимать, как он работает
  225. github.com/jupiter-tools Pull requests & feature requests are welcome! antkorwin t.me/test_tools