Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Korovin Anatoliy

May 18, 2019
Tweet

Other Decks in Programming

Transcript

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

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

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

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

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  5. Task C UUID id; String name; int estimate; TaskRepository I

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    dbUnitRule = DBUnitRule.instance(() -> { ... }); @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer(); }
  20. PostgreSQL Camunda BPM BasePostgresTest A External Monolith BaseRabbitMqTest A //

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

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

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  23. Document Service Complaint Service Search Service PostgreSQL MongoDB Elastic Search

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

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

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

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

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

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  29. 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
  30. 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
  31. 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
  32. 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
  33. Document Service Complaint Service Search Service HR Service Template Service

    Message Service Timer Service // сервисов // становится // больше
  34. 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
  35. 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 // сервисов // становится // больше
  36. 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
  37. abstract-tests.jar // вынесем в отдельную зависимость Auth Service Document Service

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

    A BaseElasticTest A BaseMvcTest A BasePostgresTest A BaseRabbitMqTest A BaseActiveMqTest A
  39. 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
  40. 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 // разделим по логике
  41. mvc-test.jar postgres-test.jar // А что, если нужно и то и

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

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

    BasePostgresTest A BaseRabbitMqTest A BaseElasticTest A BaseMongoTest A BaseMvcTest A
  44. // и так придется делать для каждой комбинации 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
  45. // и так придется делать для каждой комбинации 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
  46. Еще один абстрактный класс // как бы мы сделали это

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

    C void create() {..} void get() {..} void delete() {..} PostgreSQL MVC Context Сonfig
  48. 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!
  49. public class TaskServiceTest extends BasePostgresMvcTest { @Test @DataSet(value = "datasets/task.json”)

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

    { ... } } // заменим наследование мета-аннотациями
  51. @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);! }! }
  52. @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);! }! }
  53. @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);! }! }
  54. @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);! }! }
  55. @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);! }! }
  56. @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"); } }
  57. @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"); } }
  58. @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"); } }
  59. { "com.antkorwin.springtestmongo.Bar": [ { "id” : "18300a", "data” : "Wild

    Coyote" }, { "id” : "55ffed", "data” : "A New Hope" } ] }
  60. @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"); } }
  61. @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"); } }
  62. @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); }
  63. @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); }
  64. @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); }
  65. @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); }
  66. @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); }
  67. { "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" } ] }
  68. { "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" } ] }
  69. @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)); }
  70. @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)); }
  71. @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)); }
  72. @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)); }
  73. @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)); }
  74. @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)); }
  75. { "com.antkorwin.starwars.rabbitmq.Dreadnought": [ { "stormtroopers” : "20000" } ], "com.antkorwin.starwars.rabbitmq.XWing":[

    { "name” : "Alpha", "stormtroopers” : 3 }, { "name” : "Bravo", "stormtroopers” : 4 } ] }
  76. @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)); }
  77. План . - что мы хотим тестировать - как тестировали

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  78. 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()); } }
  79. 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()); } }
  80. 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()); } }
  81. 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()); } } // что не так?
  82. 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()); } } // зачем так?
  83. 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()); } } // зачем так? хотим один контейнер , на прогон всех тестов,
  84. 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
  85. 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); } }
  86. 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); } }
  87. 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); } }
  88. 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); } }
  89. 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); } }
  90. 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); } }
  91. private void markAsStarted(ExtensionContext context) {, , context.getRoot(), .getStore(NAMESPACE), .put(TESTCONTAINERS_PG_TAG, true);,

    }, Шарим данные между: -  тестовыми методами -  тестовыми классами // используем ExtensionContext
  92. static init. block custom before suite junit4 и junit5 less

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

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

    clean code прост в написании легко компоновать junit-platform.properties, more clean code только junit5 сложная конструкция переопределение
  95. // Выводы С JUnit5 можно уменьшить дублирование кода в микросервисных

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

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

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

    монолит - тесты в микросервисах - проблемы сопровождения - как использовать docker - что может пойти не так
  99. 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
  100. 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)
  101. @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
  102. @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
  103. @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
  104. @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
  105. @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
  106. RabbitMqExtension newInstance SpringExtension static { ... } <clinit> Create Extensions

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

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

    TestExecutionListener DependencyInjectionTestExecutionListener postProcess TestInstance TestContextManager prepareTestInstance:
  109. SpringApplication prepareEnvironment ConfigFileApplicationListener ApplicationEnvironmentPreparedEvent EPP 1 EPP 2 EPP 3

    Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod
  110. SpringApplication prepareEnvironment ConfigFileApplicationListener ApplicationEnvironmentPreparedEvent EPP 1 EPP 2 EPP 3

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

    prepareEnvironment prepareContext refreshContext BeanFactories BeanPostProcessors ConfigurationPropertiesBindingPostProcessor foreach:
  112. Create Extensions BeforeAll PostProcess TestInstance Load Context Invoke TestMethod @SpringBootTest

    @ExtendWith(SpringExtension.class) @EnableRabbitMqTestContainers class CTest { @Test void sendMessageTest() { ... } }
  113. Load Context Prepare Environment Test Run EMPTY CACHE container.start(); !

    setProperty(”host”, …);! setProperty(”port”, …);
  114. 100 10 интеграционных тестов уникальных контекстов // Может без КЭША

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

    проектах Мета-аннотации помогают собрать нужную конфигурацию теста, как из конструктора Вместе с TestContainers можно использовать production-окружение в тестах Spring Context Caching – сложный механизм, нужно понимать, как он работает