$30 off During Our Annual Pro Sale. View Details »

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. JUnit – Episode 5
    TestContainers strikes back

    View Slide

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

    View Slide

  3. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. Юнит-тесты Компонент-тесты Микросервис-тесты E2E-тесты
    @DataJpaTest
    @WebMvcTest
    @SpringBootTest
    // что мы хотим тестировать
    Интеграционные-тесты

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. Monolith
    PostgreSQL
    External
    Camunda
    BPM
    // как мы тестировали монолит

    View Slide

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

    View Slide

  13. Task
    C
    UUID id;
    String name;
    int estimate;
    // тестируем работу с БД

    View Slide

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

    View Slide

  15. Task
    C
    UUID id;
    String name;
    int estimate;
    TaskRepository
    I
    @Query(“select..”)
    ListfindAll(..)
    TaskRepositoryTest
    C
    void testFindAll(){
    ..!
    }
    // тестируем работу с БД

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. // тестировать работу с базой на H2

    View Slide

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

    View Slide

  21. PostgreSQL
    BasePostgresTest
    A
    TaskRepositoryTest
    C
    void testFindAll(){
    ..!
    }
    AnyRepositoryTest
    C
    void testAny(){
    ..!
    }
    AnyRepositoryTest
    C
    void testAny(){
    ..!
    }
    TaskServiceTest
    C
    void testGetTask(){
    ..!
    }
    // заглянем
    // внутрь
    // тестов

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. BaseRabbitMqTest
    A
    SendFirstEventTest
    C
    void testSend(){
    ..!
    }
    EventListenerTest
    C
    void testReceive(){
    ..!
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  38. // распиливаем
    // монолит

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    // тестируем

    View Slide

  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
    // тестируем

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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
    // сервисов
    // становится
    // больше

    View Slide

  52. // Продолжать копировать базовые классы?

    View Slide

  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

    View Slide

  54. abstract-tests.jar
    // вынесем в отдельную зависимость
    Auth
    Service
    Document
    Service
    Complaint
    Service
    Search
    Service
    BaseMongoTest
    A
    BaseElasticTest
    A
    BaseMvcTest
    A
    BasePostgresTest
    A
    BaseRabbitMqTest
    A
    BaseActiveMqTest
    A

    View Slide

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

    View Slide

  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

    View Slide

  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
    // разделим по логике

    View Slide

  58. mvc-test.jar postgres-test.jar
    // А что, если нужно и то и другое?
    BaseMvcTest
    A BasePostgresTest
    A
    TaskControllerTest
    C
    void create() {..}
    void get() {..}
    void delete() {..}

    View Slide

  59. Еще один
    абстрактный класс
    // как бы мы сделали это в монолите
    PostgreSQL
    MVC
    Context
    Сonfig
    BasePostgresMvcTest
    A
    TaskControllerTest
    C
    void create() {..}
    void get() {..}
    void delete() {..}

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  64. Не – не – не,
    моя умея только копироватя,
    Сопровождать
    моя не училса

    View Slide

  65. Еще один
    абстрактный класс
    // как бы мы сделали это в монолите
    PostgreSQL
    MVC
    Context
    Сonfig
    BasePostgresMvcTest
    A
    TaskControllerTest
    C
    void create() {..}
    void get() {..}
    void delete() {..}

    View Slide

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

    View Slide

  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!

    View Slide

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

    View Slide

  69. @EnableMvcTest
    @EnablePostgresTest
    public class TaskServiceTest {
    @Test
    public void getTask() {
    ...
    }
    }
    // заменим наследование мета-аннотациями

    View Slide

  70. @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @RunWith(SpringRunner.class)
    @AutoConfigureMockMvc
    public @interface EnableMvcTests {
    }
    // что внутри @EnableMvcTests?

    View Slide

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

    View Slide

  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: }
    // Не все так просто

    View Slide

  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: }
    // Не все так просто

    View Slide

  74. JUnit4 @RunWith
    •  class level
    •  non repeatable

    View Slide

  75. // Решение

    View Slide

  76. @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @ExtendWith(SpringExtension.class)
    @DataJpaTest
    @AutoConfigureTestDatabase(replace = NONE)
    public @interface EnableDataTests {
    }
    Repeatable & Inherited
    // Мета-аннотации и ExtendWith

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  84. PostgresTestContainersExtension
    C
    EnablePostgresTestContainers
    @
    PostgreSQL

    View Slide

  85. PostgresTestContainersExtension
    C
    EnablePostgresTestContainers
    @
    PostgreSQL
    EnablePostgresIntegrationTest
    @ EnablePostgresDataTest
    @

    View Slide

  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);!
    }!
    }

    View Slide

  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);!
    }!
    }

    View Slide

  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);!
    }!
    }

    View Slide

  89. View Slide

  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);!
    }!
    }

    View Slide

  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);!
    }!
    }

    View Slide

  92. // тестируем
    валидация дата-сетов
    агрегации в mongodb

    View Slide

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

    View Slide

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

    View Slide

  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");
    }
    }

    View Slide

  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");
    }
    }

    View Slide

  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");
    }
    }

    View Slide

  98. {
    "com.antkorwin.springtestmongo.Bar": [
    {
    "id” : "18300a",
    "data” : "Wild Coyote"
    },
    {
    "id” : "55ffed",
    "data” : "A New Hope"
    }
    ]
    }

    View Slide

  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");
    }
    }

    View Slide

  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");
    }
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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"
    }
    ]
    }

    View Slide

  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"
    }
    ]
    }

    View Slide

  108. // тестируем
    валидация дата-сетов
    конвертацию данных

    View Slide

  109. RabbitMqTestContainersExtension
    C
    EnableRabbitMqTestContainers
    @
    RabbitMqMessagesExtension
    C
    ExpectedMessages
    @

    View Slide

  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));
    }

    View Slide

  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));
    }

    View Slide

  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));
    }

    View Slide

  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));
    }

    View Slide

  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));
    }

    View Slide

  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));
    }

    View Slide

  116. {
    "com.antkorwin.starwars.rabbitmq.Dreadnought": [
    {
    "stormtroopers” : "20000"
    }
    ],
    "com.antkorwin.starwars.rabbitmq.XWing":[
    {
    "name” : "Alpha",
    "stormtroopers” : 3
    },
    {
    "name” : "Bravo",
    "stormtroopers” : 4
    }
    ]
    }

    View Slide

  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));
    }

    View Slide

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

    View Slide

  119. TestContainers!

    View Slide

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

    View Slide

  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());
    }
    }

    View Slide

  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());
    }
    }

    View Slide

  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());
    }
    }

    View Slide

  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());
    }
    }
    // что не так?

    View Slide

  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());
    }
    }
    // зачем так?

    View Slide

  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());
    }
    }
    // зачем так?
    хотим один контейнер ,
    на прогон всех тестов,

    View Slide

  127. Поищем в
    JUnit5 ?
    Нужен
    BeforeSuite
    callback !

    View Slide

  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

    View Slide

  129. Возможно, скоро будет…

    View Slide

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

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  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);
    }
    }

    View Slide

  137. private void markAsStarted(ExtensionContext context) {,
    ,
    context.getRoot(),
    .getStore(NAMESPACE),
    .put(TESTCONTAINERS_PG_TAG, true);,
    },
    Шарим данные между:
    -  тестовыми методами
    -  тестовыми классами
    // используем ExtensionContext

    View Slide

  138. А удалять
    контейнеры
    кто будет?

    View Slide

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

    View Slide

  140. /resources/META-INF/services/
    org.junit.jupiter.api.extension.Extension,,,
    com.....PostgresTestExtension ,
    ,
    ,
    /resources/,
    junit-platform.properties,
    ,
    junit.jupiter.extensions.autodetection.enabled = true
    ,
    // делаем глобальный Extension

    View Slide

  141. static init. block custom before suite

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  145. static init. block custom before suite
    junit4 и junit5
    less clean code
    прост в написании
    легко компоновать
    junit-platform.properties,
    more clean code
    только junit5
    сложная конструкция
    переопределение

    View Slide

  146. DEMO

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  151. Работает:
    Не работает:
    CTest
    ATest
    BTest
    ATest
    BTest
    CTest

    View Slide

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

    View Slide

  153. KEY CONTEXT
    EMPTY_CACHE
    ATest
    BTest
    CTest

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  168. View Slide

  169. View Slide

  170. View Slide

  171. Как работает
    Spring и JUnit

    View Slide

  172. Create
    Extensions
    BeforeAll
    PostProcess
    TestInstance
    Load
    Context
    Invoke
    TestMethod

    View Slide

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

    View Slide

  174. RabbitMqExtension
    newInstance
    SpringExtension
    static {
    ...
    }

    Create
    Extensions
    BeforeAll
    PostProcess
    TestInstance
    Load
    Context
    Invoke
    TestMethod

    View Slide

  175. RabbitMqExtension
    newInstance
    SpringExtension
    static {
    ...
    }

    Create
    Extensions
    BeforeAll
    PostProcess
    TestInstance
    Load
    Context
    Invoke
    TestMethod

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  184. DependencyInjectionTestExecutionListener
    Create
    Extensions
    BeforeAll
    PostProcess
    TestInstance
    Load
    Context
    Invoke
    TestMethod

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  196. // The Force here:

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  210. Create
    Extensions
    BeforeAll
    PostProcess
    TestInstance
    Load
    Context
    Invoke
    TestMethod
    @SpringBootTest
    @ExtendWith(SpringExtension.class)
    @EnableRabbitMqTestContainers
    class CTest {
    @Test
    void sendMessageTest() {
    ...
    }
    }

    View Slide

  211. container.start();
    !
    setProperty(”host”, …);!
    setProperty(”port”, …);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  216. View Slide

  217. View Slide

  218. View Slide

  219. // Что делать?
    DarthVaderTest

    View Slide

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

    View Slide

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

    View Slide

  222. JediTest
    YodaTest
    AnakinSkywalkerTest
    JarJarBinksTest
    BobaFettTest
    ObiWanKenobiTest
    DartMaulTest
    LukeSkywalkerTest
    DarthVaderTest
    // Что делать?
    Turn to the dark side
    Context mutation

    View Slide

  223. 100
    10
    интеграционных тестов
    уникальных контекстов
    // Может без КЭША обойдемся?
    ~12 sec старт нового контекста
    2 min
    20 min
    с кэшем
    без кэша

    View Slide

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

    View Slide

  225. github.com/jupiter-tools
    Pull requests &
    feature requests
    are welcome!
    antkorwin
    t.me/test_tools

    View Slide