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

Curse Of Spring Boot Test [version for QA]

Curse Of Spring Boot Test [version for QA]

Developers and tests. There are different opinions, and Kirill himself thinks that developers should write tests. In this session, he'll talk about tests that help developers write code and verify already written code on an app level.

Being a Java developer, Kirill will do it through old and familiar JUnit and spring-boot-test frameworks, paying extra attention to special aspects of the work of spring-boot-test when testing on the boundary of different app components (@RestController, @Component, @Service, Repository...). He'll try to dispel some magic which frameworks do in your stead to bring some more awareness to your test writing.

Kirill Tolkachev

December 07, 2018
Tweet

More Decks by Kirill Tolkachev

Other Decks in Technology

Transcript

  1. Тестируем и плачем
    Вместе со Spring Boot Test

    View Slide

  2. @tolkv
    @lavcraft

    View Slide

  3. @jekaborisov
    @jeka1978

    View Slide

  4. В программе
    Тестирование живого приложения
    ● Старые подходы
    ○ @ContextConfiguration
    ○ @ContextHierarchy && @DirtiesContext
    ○ @ActiveProfiles
    ● Что нового нам приготовил Spring Boot?
    ○ @SpringBootTest
    ○ @TestConfiguration
    ○ @MockBean && @SpyBean && @*Beans
    ○ @DataJpaTest
    ○ @MvcTest
    ● Кэширование spring контекстов
    ● Шкала тестов

    View Slide

  5. Немного теории

    View Slide

  6. Шкала Тестов
    Unit
    Component Test
    Microservice Test
    System Test




    View Slide

  7. Шкала Тестов
    Unit
    Component Test
    Microservice Test
    System Test




    View Slide

  8. Unit/Component тесты. Для чего?

    View Slide

  9. Unit/Component тесты. Для чего?

    View Slide

  10. Unit/Component тесты. Для чего?
    Ваши тесты
    Тут

    View Slide

  11. Unit/Component тесты. Для чего?
    Тесты
    уменьшают
    неопределённость

    View Slide

  12. Есть два типа тестов
    Простой Сложный

    View Slide

  13. Есть два типа тестов
    Какой сам выберешь
    Простой Сложный

    View Slide

  14. Есть два типа тестов
    Какой сам выберешь,
    а какой разработчику оставишь?
    Простой Сложный

    View Slide

  15. Когда пишут тесты?

    View Slide

  16. Когда пишут тесты?
    1. Требование заказчика

    View Slide

  17. Когда пишут тесты?
    1. Требование заказчика
    2. Культура

    View Slide

  18. Когда пишут тесты?
    1. Требование заказчика
    2. Культура Перед кодом

    View Slide

  19. Когда пишут тесты?
    1. Требование заказчика
    2. Культура Перед кодом
    Вместе кодом

    View Slide

  20. Когда пишут тесты?
    1. Требование заказчика
    2. Культура Перед кодом
    Вместе кодом
    После кода

    View Slide

  21. Unit
    Component Test
    Про какие тесты будем говорить?


    Перед кодом
    Вместе кодом
    После кода

    View Slide

  22. Unit
    Component Test


    Про какие тесты будем говорить?
    Перед кодом
    Вместе кодом
    После кода

    View Slide

  23. Начнём

    View Slide

  24. router
    joker
    jbaruch-assistant
    Default
    Answers
    Database
    Joker Resolver
    $tokens.joker
    JBaruch Resolver
    ...
    $tokens.jbaruch
    Дано

    View Slide

  25. Дано
    Чат поддержки тестировщиков
    assistant
    joker-assistant
    jbaruch-assistant
    Queue
    мы
    web
    rest
    rest
    rest
    Default
    Answers
    Database

    View Slide

  26. Эксперты

    View Slide

  27. Эксперты

    View Slide

  28. Demo

    View Slide

  29. Дано
    Чат поддержки тестировщиков
    assistant
    joker-assistant
    jbaruch-assistant
    Queue
    мы
    web
    rest
    rest
    rest
    Default
    Answers
    Database

    View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. А давайте тестировать

    View Slide

  34. А давайте тестировать
    router
    joker
    jbaruch-assistant
    Default
    Answers
    Database
    Joker Resolver
    $tokens.joker
    JBaruch Resolver
    ...
    $tokens.jbaruch

    View Slide

  35. Demo
    JokerWordsFrequencyResolverTest

    View Slide

  36. А давайте тестировать. Тест #1
    1. Пишем JokerWordsFrequencyResolverTest.

    View Slide

  37. Demo

    View Slide

  38. Кого тестируем
    @Component
    public class JokerWordsFrequencyResolver extends AbstractWordsFreqResolver {
    @Value("${tokens.joker}")
    private String answers;
    public JokerWordsFrequencyResolver(WordsComposer wordsComposer) {
    super(wordsComposer);
    }
    @Override
    public QuestionType getQuestionType() {
    return JOKER;
    }
    }

    View Slide

  39. Тест №1.5
    public class JokerWordsFrequencyResolverTest {
    @Test
    public void name() throws Exception {
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver =
    new JokerWordsFrequencyResolver(
    ...
    )
    );
    jokerWordsFrequencyResolver.setAnswers( "objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  40. Тест №1.5
    public class JokerWordsFrequencyResolverTest {
    @Test
    public void name() throws Exception {
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver =
    new JokerWordsFrequencyResolver(
    new WordsComposer(
    ...
    )
    );
    jokerWordsFrequencyResolver.setAnswers( "objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  41. Тест №1.5
    public class JokerWordsFrequencyResolverTest {
    @Test
    public void name() throws Exception {
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver =
    new JokerWordsFrequencyResolver(
    new WordsComposer(
    new GarbageProperties()
    )
    );
    jokerWordsFrequencyResolver.setAnswers( "objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  42. Тест №1
    public class JokerWordsFrequencyResolverTest {
    @Test
    public void name() throws Exception {
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver =
    new JokerWordsFrequencyResolver(
    new WordsComposer(
    new GarbageProperties()
    )
    );
    jokerWordsFrequencyResolver.setAnswers( "objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  43. Тест №1
    public class JokerWordsFrequencyResolverTest {
    @Test
    public void name() throws Exception {
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver =
    new JokerWordsFrequencyResolver(
    new WordsComposer(
    new GarbageProperties()
    )
    );
    jokerWordsFrequencyResolver.setAnswers( "objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  44. Результат
    java.lang.NullPointerException
    at … .(WordsComposer.java:48)

    View Slide

  45. Not Passed

    View Slide

  46. WordsComposer:48
    garbageProperties.getGarbage()
    .contains(s.toLowerCase())
    NullPointerException

    View Slide

  47. WordsComposer:48
    @Value("${garbage}")
    void setGarbage(String[] garbage) {
    Запчасти Spring

    View Slide

  48. А давайте тестировать. Тест #1
    1. Пишем JokerWordsFrequencyResolverTest.
    2. Как ни крути, но нужен более “интеграционный тест”

    View Slide

  49. Шкала Тестов
    Unit

    View Slide

  50. Шкала Тестов
    Unit
    Component Test


    View Slide

  51. Unit
    Component Test


    Про какие тесты будем говорить?
    Перед кодом
    Вместе кодом
    После кода

    View Slide

  52. Unit
    Component Test


    Про какие тесты будем говорить?
    Перед кодом
    Вместе кодом
    После кода

    View Slide

  53. Unit
    Component Test


    Про какие тесты будем говорить?
    Перед кодом
    Вместе кодом
    После кода
    @Value("${garbage}")
    void setGarbage(String[] garbage) {
    Инициализируется
    Spring`ом

    View Slide

  54. Ещё немного теории

    View Slide

  55. IoC, DI, Spring и друзья

    View Slide

  56. IoC, DI, Spring и друзья

    View Slide

  57. IoC, DI, Spring и друзья

    View Slide

  58. IoC, DI, Spring и друзья
    Кино про
    супергероев

    View Slide

  59. IoC, DI, Spring и друзья
    Кино про
    супергероев

    View Slide

  60. IoC, DI, Spring и друзья
    Кино про
    супергероев

    View Slide

  61. IoC, DI, Spring и друзья
    Кино про
    супергероев
    IoC

    View Slide

  62. ФабрикаГероев
    IoC, DI, Spring и друзья
    Кино про
    супергероев
    IoC

    View Slide

  63. IoC, DI, Spring и друзья
    IoC для инверсии поведения

    View Slide

  64. IoC, DI, Spring и друзья
    public class СуперЗлодейТест {
    @Before
    public void setUp() throws Exception {
    ...
    }
    }
    Тоже инверсия
    контроля
    IoC для инверсии поведения

    View Slide

  65. IoC, DI, Spring и друзья
    public class СъемочнаяПлощадка {
    public static void main(String[] args) {
    Киношка съёмка = new Киношка().снимать();
    съёмка.герой.бить();
    съёмка.злодей.страдать();
    съёмка.злодей.бить();
    съёмка.герой.страдать();
    съёмка.герой.страдать();
    }
    }

    View Slide

  66. IoC, DI, Spring и друзья
    public class СъемочнаяПлощадка {
    public static void main(String[] args) {
    Киношка съёмка = new Киношка().снимать();
    съёмка.герой.бить();
    съёмка.злодей.страдать();
    съёмка.злодей.бить();
    съёмка.герой.страдать();
    съёмка.герой.страдать();
    }
    }
    NullPointerException

    View Slide

  67. IoC, DI, Spring и друзья
    public class Киношка {
    СуперГерой герой;
    СуперЗлодей злодей;
    public Киношка снимать() {
    return new Киношка();
    }
    }

    View Slide

  68. IoC, DI, Spring и друзья
    public class СуперГерой implements Герой {
    private СуперЗлодей вражина;
    @Override
    public void бить() {
    вражина.бить();
    }
    }
    public class СуперЗлодей implements Герой {
    private СуперГерой вражина;
    @Override
    public void бить() {
    вражина.страдать();
    }
    }
    Кто проставляет?

    View Slide

  69. public class ФабрикаГероев {
    public Object родить() {
    if (new Random().nextBoolean()) {
    return new СуперГерой();
    }
    return new СуперЗлодей();
    }
    }
    IoC, DI, Spring и друзья

    View Slide

  70. public class ФабрикаГероев {
    public Object родить() {
    if (new Random().nextBoolean()) {
    return new СуперГерой();
    }
    return new СуперЗлодей();
    }
    }
    IoC, DI, Spring и друзья

    View Slide

  71. IoC, DI, Spring и друзья
    Spring

    View Slide

  72. IoC, DI, Spring и друзья
    Spring
    ● @Autowired
    ● @Component/@Service
    ● @Configuration

    View Slide

  73. IoC, DI, Spring и друзья
    @Component
    public class Киношка {
    @Autowired СуперГерой герой;
    @Autowired СуперЗлодей злодей;
    public static Киношка снимать() {
    return new Киношка();
    }
    }
    Spring
    ● @Autowired
    ● @Component/@Service
    ● @Configuration

    View Slide

  74. IoC, DI, Spring и друзья
    @Component
    public class Киношка {
    @Autowired СуперГерой герой;
    @Autowired СуперЗлодей злодей;
    public static Киношка снимать() {
    return new Киношка();
    }
    }
    Spring
    ● @Autowired
    ● @Component/@Service
    ● @Configuration

    View Slide

  75. IoC, DI, Spring и друзья
    @Component
    public class СуперГерой implements Герой {
    @Autowired СуперЗлодей вражина;
    @Override
    public void бить() {
    вражина.бить();
    }
    }
    Spring
    ● @Autowired
    ● @Component/@Service
    ● @Configuration

    View Slide

  76. Demo

    View Slide

  77. Тест №1.5
    @RunWith(SpringRunner. class)
    @ContextConfiguration (classes = JokerWordsFrequencyResolverTestConfig. class)
    public class JokerWordsFrequencyResolverTest {
    @Autowired
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver;
    @Test
    public void name() throws Exception {
    jokerWordsFrequencyResolver.setAnswers("objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  78. Тест №1.5
    @Configuration
    public class JokerWordsFrequencyResolverTestConfig {
    @Bean
    public JokerWordsFrequencyResolver jokerWordsFrequencyResolver(
    WordsComposer wordsComposer) {
    return new JokerWordsFrequencyResolver(wordsComposer);
    }
    }

    View Slide

  79. Тест №1.5
    @Configuration
    public class JokerWordsFrequencyResolverTestConfig {
    @Bean
    public JokerWordsFrequencyResolver jokerWordsFrequencyResolver(
    WordsComposer wordsComposer) {
    return new JokerWordsFrequencyResolver(wordsComposer);
    }
    }

    View Slide

  80. Тест №1.5
    @Configuration
    @ComponentScan("com.conference.spring.test.common")
    public class JokerWordsFrequencyResolverTestConfig {
    @Bean
    public JokerWordsFrequencyResolver jokerWordsFrequencyResolver(
    WordsComposer wordsComposer) {
    return new JokerWordsFrequencyResolver(wordsComposer);
    }
    }

    View Slide

  81. Тест №1.5
    @Configuration
    @ComponentScan("com.conference.spring.test.common")
    public class JokerWordsFrequencyResolverTestConfig {
    @Bean
    public JokerWordsFrequencyResolver jokerWordsFrequencyResolver(
    WordsComposer wordsComposer) {
    return new JokerWordsFrequencyResolver(wordsComposer);
    }
    }

    View Slide

  82. Тест №1.5
    @RunWith(SpringRunner. class)
    @ContextConfiguration (classes = JokerWordsFrequencyResolverTestConfig. class)
    public class JokerWordsFrequencyResolverTest {
    @Autowired
    JokerWordsFrequencyResolver jokerWordsFrequencyResolver;
    @Test
    public void name() throws Exception {
    jokerWordsFrequencyResolver.setAnswers("objects");
    int match = jokerWordsFrequencyResolver.match(
    Question. builder().body("objects ...").build());
    assertThat(match, equalTo(1));
    }
    }

    View Slide

  83. Passed

    View Slide

  84. Ещё немного теории

    View Slide

  85. SpringRunner
    /**
    * @author Sam Brannen
    * @since 4.3
    * @see SpringJUnit4ClassRunner
    */
    public final class SpringRunner extends SpringJUnit4ClassRunner

    View Slide

  86. SpringRunner & SpringJUnit4ClassRunner
    /**
    * @author Sam Brannen
    * @author Juergen Hoeller
    * ...
    */
    public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner

    View Slide

  87. /**
    * {@code SpringExtension} integrates the Spring TestContext …
    * into JUnit 5's Jupiter programming model.
    ...
    * @author Sam Brannen
    * @since 5.0
    */
    public class SpringExtension implements BeforeAllCallback, … {
    SpringExtension — Junit5

    View Slide

  88. SpringExtension — Junit5
    @SpringJUnitConfig
    @SpringJUnitWebConfig

    View Slide

  89. 1. Пишем TextBasedQuestionTypeResolverTest
    А давайте тестировать. Тест #2

    View Slide

  90. Шкала Тестов
    Unit
    Component Test


    View Slide

  91. 1. Пишем TextBasedQuestionTypeResolverTest
    2. Вручную создаем три бина для тестирования
    TextBasedQuestionTypeResolver на примере Барух vs Джокер кейса
    А давайте тестировать. Тест #2

    View Slide

  92. Demo
    TextBasedQuestionTypeResolverTest

    View Slide

  93. @RunWith(SpringRunner. class)
    @ContextConfiguration (classes = TextBasedQuestionTypeResolverTestConfig. class)
    public class TextBasedQuestionTypeResolverTest {
    @Autowired
    TextBasedQuestionTypeResolver questionResolver;
    @Test
    public void name() throws Exception {
    QuestionType groovy = questionResolver.resolveType(new Question("groovy"));
    QuestionType objects = questionResolver.resolveType(new Question("псих"));
    assertThat(groovy, equalTo(JBARUCH));
    assertThat(objects, equalTo(JOKER));
    }
    }
    Тест #2

    View Slide

  94. @Configuration
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    }
    Тест #2

    View Slide

  95. @Configuration
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2

    View Slide

  96. @Configuration
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2
    Для них нужен WordsComposer
    @ComponentScan("com.conference.spring.test.common") ?

    View Slide

  97. @Configuration
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2
    Для них нужен WordsComposer
    @ComponentScan("com.conference.spring.test.common") ?

    View Slide

  98. @Configuration
    @Import(CommonConfig. class)
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2

    View Slide

  99. @Configuration
    @ComponentScan("com.conference.spring.test.common")
    public class CommonConfig {
    }
    Тест #2

    View Slide

  100. Not Passed

    View Slide

  101. class JokerWordsFrequencyResolver
    @Value("${tokens.joker}")
    private String answers;
    class JBaruchWordsFrequencyResolver
    @Value("${tokens.jbaruch}")
    private String answers;
    Кто считывает?
    Что случилось

    View Slide

  102. Что случилось
    class JokerWordsFrequencyResolver
    @Value("${tokens.joker}")
    private String answers;
    class JBaruchWordsFrequencyResolver
    @Value("${tokens.jbaruch}")
    private String answers;
    application.yml:
    tokens:
    jbaruch: npm leftpad artifactory groovy object ***
    joker: objects
    Кто считывает?
    Отсюда считываем

    View Slide

  103. @Configuration
    @Import(CommonConfig.class)
    @PropertySource("classpath*:application.yml")
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2

    View Slide

  104. @Configuration
    @Import(CommonConfig.class)
    @PropertySource("classpath*:application.yml")
    public class TextBasedQuestionTypeResolverTestConfig {
    @Bean
    public TextBasedQuestionTypeResolver textBasedQuestionTypeResolver(
    List c) {
    return new TextBasedQuestionTypeResolver(c);
    }
    @Bean
    public JokerWordsFrequencyResolver … { … }
    @Bean
    public JBaruchWordsFrequencyResolver … { … }
    }
    Тест #2

    View Slide

  105. Not Passed

    View Slide

  106. 1. Пишем TextBasedQuestionTypeResolverTest
    2. Вручную создаем три бина для тестирования
    TextBasedQuestionTypeResolver на примере Барух vs Егор кейса
    3. Все падает потому что не подтягивается application.yml
    4. @PropertySource …
    А давайте тестировать. Тест #2

    View Slide

  107. @ContextConfiguration(classes = ....class,
    initializers =
    YamlFileApplicationContextInitializer.class)
    public class OurTest {
    @Test
    public test(){
    ...
    }
    }
    А давайте тестировать. Тест #2

    View Slide

  108. Spring Boot обновки
    1. @SpringBootTest
    2. @TestConfiguration
    3. @MockBean && @SpyBean
    4. @DataJpaTest
    5. @MockMvcTest

    View Slide

  109. Углубляемся в Spring. Тест #2
    1. Применяем @SpringBootTest

    View Slide

  110. Demo

    View Slide

  111. @RunWith(SpringRunner. class)
    @ContextConfiguration (classes = TextBasedQuestionTypeResolverTestConfig. class)
    public class TextBasedQuestionTypeResolverTest {
    @Autowired
    TextBasedQuestionTypeResolver questionResolver;
    @Test
    public void name() throws Exception {
    QuestionType groovy = questionResolver.resolveType(new Question("groovy"));
    QuestionType objects = questionResolver.resolveType(new Question("objects"));
    assertThat(groovy, equalTo(JBARUCH));
    assertThat(objects, equalTo(JOKER));
    }
    }
    Тест #2

    View Slide

  112. @RunWith(SpringRunner. class)
    @SpringBootTest
    public class TextBasedQuestionTypeResolverTest {
    @Autowired
    TextBasedQuestionTypeResolver questionResolver;
    @Test
    public void name() throws Exception {
    QuestionType groovy = questionResolver.resolveType(new Question("groovy"));
    QuestionType objects = questionResolver.resolveType(new Question("objects"));
    assertThat(groovy, equalTo(JBARUCH));
    assertThat(objects, equalTo(JOKER));
    }
    }
    Тест #2

    View Slide

  113. Not Passed

    View Slide

  114. @RunWith(SpringRunner. class)
    @SpringBootTest
    @ActiveProfiles ("joker_vs_jbaruch")
    public class TextBasedQuestionTypeResolverTest {
    @Autowired
    TextBasedQuestionTypeResolver questionResolver;
    @Test
    public void name() throws Exception {
    QuestionType groovy = questionResolver.resolveType(new Question("groovy"));
    QuestionType objects = questionResolver.resolveType(new Question("objects"));
    assertThat(groovy, equalTo(JBARUCH));
    assertThat(objects, equalTo(JOKER));
    }
    }
    Тест #2
    Для подгрузки
    application-joker_vs_jbaruch.yml

    View Slide

  115. Passed

    View Slide

  116. Углубляемся в Spring. Тест #2
    1. Применяем @SpringBootTest
    2. Долго…
    3. @SpringBootTest(classes = ...class)

    View Slide

  117. Углубляемся в Spring. Тест #2
    1. Применяем @SpringBootTest
    2. Долго…
    3. @SpringBootTest(classes = ...class)
    4. Стало быстрее

    View Slide

  118. Demo - но можно лучше

    View Slide

  119. @Configuration
    @ComponentScan("com.conference.spring.test.common")
    public class CommonConfig {
    @PostConstruct
    public void init() {
    System.out.println("Only once " + CommonConfig.class);
    }
    }
    Тест #2

    View Slide

  120. Запустим тест №1 и №2 за раз

    View Slide

  121. Only once

    only once

    only once

    View Slide

  122. Only once

    only once

    only once
    Дважды...

    View Slide

  123. Углубляемся в Spring. Тест #2
    1. Применяем @SpringBootTest
    2. Долго…
    3. @SpringBootTest(classes = ...class)
    4. Стало быстрее
    5. С кэшированием конфигураций – еще быстрее

    View Slide

  124. Углубляемся в Spring. Тест #2
    @ContextHierarchy({
    @ContextConfiguration(classes=WordsCommonConfiguration.class),
    @ContextConfiguration(classes= ...class)
    })

    View Slide

  125. Demo

    View Slide

  126. @SpringBootTest
    @ContextHierarchy({
    @ContextConfiguration(classes =
    TextBasedQuestionTypeResolverTestConfig.class),
    @ContextConfiguration(classes = CommonConfig.class)
    })
    @ActiveProfiles("joker_vs_jbaruch")
    @RunWith(SpringRunner.class)
    public class TextBasedQuestionTypeResolverTest {
    ...

    View Slide

  127. Запустим тест №1 и №2 за раз

    View Slide

  128. Only once

    only once

    only once

    only once

    only once
    Четыре раза...

    View Slide

  129. View Slide

  130. @Configuration
    @Import(CommonConfig.class)
    public class JokerWordsFrequencyResolverTestConfig {
    @Configuration
    @Import(CommonConfig.class)
    public class TextBasedQuestionTypeResolverTestConfig {

    View Slide

  131. Убираем
    @Import(CommonConfig.class)

    View Slide

  132. Not Passed

    View Slide

  133. View Slide

  134. Не найден spring bean WordsComposer

    View Slide

  135. Углубляемся в Spring. Тест #2
    @ContextHierarchy({
    @ContextConfiguration(classes=WordsCommonConfiguration.class),
    @ContextConfiguration(classes= ...class)
    })
    Порядок важен! Т.к другая конфигурация использует бины из
    WordsCommonConfiguration

    View Slide

  136. Меняем порядок в @ContextHierarchy
    @SpringBootTest
    @ContextHierarchy({
    @ContextConfiguration(classes = CommonConfig.class),
    @ContextConfiguration(classes =
    TextBasedQuestionTypeResolverTestConfig.class)
    })
    @ActiveProfiles("joker_vs_jbaruch")
    @RunWith(SpringRunner.class)
    public class TextBasedQuestionTypeResolverTest {
    ...
    CommonConfig теперь первый

    View Slide

  137. Passed

    View Slide

  138. Only once

    only once

    only once
    Дважды...

    View Slide

  139. Сделали круг

    View Slide

  140. Опять не закешировалось. Тест #2

    View Slide

  141. Правила кэширования контекстов. Тест #2
    @SpringBootTest – должен быть везде
    @Import – должен быть нигде
    @ActiveProfiles – один на всех
    SpringBootTest.properties – должны быть одинаковые

    View Slide

  142. Правила кэширования контекстов. Тест #2
    @SpringBootTest – должен быть везде
    @Import – должен быть нигде
    @ActiveProfiles – один на всех
    SpringBootTest.properties – должны быть одинаковые

    View Slide

  143. Правила кэширования контекстов. Тест #2
    @SpringBootTest – должен быть везде
    @Import – должен быть нигде
    @ActiveProfiles – один на всех
    SpringBootTest.properties – должны быть одинаковые
    Порядок важен!
    Любая перестановка – cache miss

    View Slide

  144. Правила кэширования контекстов. Тест #2
    @SpringBootTest(properties={"a=b","b=a"})
    @SpringBootTest(properties={"b=a","a=b"})

    View Slide

  145. Правила кэширования контекстов. Тест #2
    @SpringBootTest(properties={"a=b","b=a"})
    @SpringBootTest(properties={"b=a","a=b"})
    Кэш не сработает

    View Slide

  146. Правила кэширования контекстов. Тест #2
    @SpringBootTest – должен быть везде
    @Import – должен быть нигде
    @ActiveProfiles – один на всех
    SpringBootTest.properties – должны быть одинаковые

    View Slide

  147. Хрупкий кэш
    Все может привести к потере кэша

    View Slide

  148. Пользуемся силой
    logging.level.org.springframework.test.context.cache=debug

    View Slide

  149. Б – безопасность
    @SpringBootTest
    @ActiveProfiles("joker_vs_jbaruch")
    public abstract class ResolversAbstractCommonConfiguration {
    }

    View Slide

  150. Only once

    only once
    Один!...

    View Slide

  151. А если наоборот? (как не кэшировать)

    View Slide

  152. А если наоборот? (как не кэшировать)
    @DirtiesContext(...)

    View Slide

  153. А если наоборот? (как не кэшировать)
    @DirtiesContext(...)
    methodMode() default MethodMode.AFTER_METHOD
    classMode() default ClassMode.AFTER_CLASS
    ...

    View Slide

  154. Проверим наши знания. Тест #3
    1. протестируем AnswerCacheServiceJPABackend

    View Slide

  155. Demo
    AnswerCacheServiceJPABackend

    View Slide

  156. Что тестируем
    @Service
    @RequiredArgsConstructor
    public class AnswerCacheServiceJPABackend implements AnswerCacheService {
    private final QuestionRepository questionRepository;
    private final AnswersRepository answersRepository;
    @Override
    public Answer find(Question question) { … }

    }

    View Slide

  157. Что тестируем
    @Service
    @RequiredArgsConstructor
    public class AnswerCacheServiceJPABackend implements AnswerCacheService {
    private final QuestionRepository questionRepository;
    private final AnswersRepository answersRepository;
    @Override
    public Answer find(Question question) { … }

    }

    View Slide

  158. Что тестируем
    @Service
    @RequiredArgsConstructor
    public class AnswerCacheServiceJPABackend implements AnswerCacheService {
    private final QuestionRepository questionRepository;
    private final AnswersRepository answersRepository;
    @Override
    public Answer find(Question question) { … }

    }

    View Slide

  159. Spring Boot обновки
    1. @SpringBootTest
    2. @MockBean && @SpyBean
    3. @TestConfiguration
    4. @DataJpaTest
    5. @MockMvcTest

    View Slide

  160. Как тестируем
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AnswerCacheServiceJPABackendTestConfig.class)
    public class AnswerCacheServiceJPABackendTest {
    @Autowired
    AnswerCacheService answerCacheService;
    @MockBean
    AnswersRepository answersRepository;
    @MockBean
    QuestionRepository questionRepository;
    @Test
    public void should_not_fail() throws Exception {
    … test …
    }
    }

    View Slide

  161. Как тестируем
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AnswerCacheServiceJPABackendTestConfig.class)
    public class AnswerCacheServiceJPABackendTest {
    @Autowired
    AnswerCacheService answerCacheService;
    @MockBean
    AnswersRepository answersRepository;
    @MockBean
    QuestionRepository questionRepository;
    @Test
    public void should_not_fail() throws Exception {
    … test …
    }
    }

    View Slide

  162. Как тестируем
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = AnswerCacheServiceJPABackendTestConfig.class)
    public class AnswerCacheServiceJPABackendTest {
    @Autowired
    AnswerCacheService answerCacheService;
    @MockBean
    AnswersRepository answersRepository;
    @MockBean
    QuestionRepository questionRepository;
    @Test
    public void should_not_fail() throws Exception {
    … test …
    }
    }

    View Slide

  163. Как тестируем – Конфигурация
    @Configuration
    public class AnswerCacheServiceJPABackendTestConfig {
    @Bean
    public AnswerCacheServiceJPABackend answerCacheServiceJpaBackend(
    QuestionRepository qR,
    AnswersRepository aR) {
    return new AnswerCacheServiceJPABackend(qR, aR);
    }
    }

    View Slide

  164. Как тестируем – сам тест
    @Test
    public void should_not_fail() throws Exception {
    Mockito.doThrow(new RuntimeException("Database is down"))
    .when(questionRepository)
    .findFirstByText(Matchers.anyString());
    Answer answer = answerCacheService.find(Question.builder().build());
    assertNull(answer);
    }
    }
    Наш @MockBean

    View Slide

  165. Passed

    View Slide

  166. Синергия с Mockito
    1. @MockBean/@SpyBean
    2. @PostConstruct для настройки
    3. @Bean для настройки конкретных моков

    View Slide

  167. 1. Запустим все тесты
    Все ли хорошо?

    View Slide

  168. Not Passed

    View Slide

  169. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    Все ли хорошо?
    Стандартный тест на
    запуск контекст
    см start.spring.io

    View Slide

  170. Почему упал
    Два бина одного типа в контексте
    ● answerCacheServiceJPABackend
    ● answerCacheServiceJpaBackend

    View Slide

  171. Почему упал
    Два бина одного типа в контексте
    ● answerCacheServiceJPABackend
    ● answerCacheServiceJpaBackend

    View Slide

  172. Как Spring называет
    бины?
    почему имена бинов разные

    View Slide

  173. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    3. Загрузил бины из другого теста!
    Все ли хорошо?

    View Slide

  174. View Slide

  175. Spring Boot обновки
    1. @SpringBootTest
    2. @MockBean && @SpyBean
    3. @TestConfiguration
    4. @DataJpaTest
    5. @MockMvcTest

    View Slide

  176. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    3. Загрузил бины из другого теста!
    4. @TestConfiguration!
    Все ли хорошо?

    View Slide

  177. 1. Не сканируется @SpringBootTest
    2. Не сканируется другими конфигурациями и тестами
    3. Не прерывает процесс сканирования @SpringBootTest
    @TestConfiguration

    View Slide

  178. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    3. Загрузил бины из другого теста!
    4. @TestConfiguration!
    5. DeveloperAssistantApplicationTests.contextLoad работает
    Все ли хорошо?

    View Slide

  179. Почему упал
    Два бина одного типа в контексте
    ● answerCacheServiceJPABackend
    ● answerCacheServiceJpaBackend

    View Slide

  180. Почему упал
    Два бина одного типа в контексте
    ● answerCacheServiceJPABackend
    ● answerCacheServiceJpaBackend
    Опять двадцать пять!

    View Slide

  181. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    3. Загрузил бины из другого теста!
    4. @TestConfiguration!
    5. DeveloperAssistantApplicationTests.contextLoad работает
    6. А AnswerCacheServiceJPABackendTest перестал
    7. Загрузил бины из другого теста!
    Все ли хорошо?

    View Slide

  182. Spring Заговор

    View Slide

  183. 1.
    Как @SpringBootTest сканирует пакеты

    View Slide

  184. Два процесса сканирования
    1. @SpringBootTest сканирование
    2. @SpringBootApplication (@ComponentScan)

    View Slide

  185. Два процесса сканирования
    1. @SpringBootTest сканирование
    2. @SpringBootApplication (@ComponentScan)
    Вверх

    View Slide

  186. Два процесса сканирования
    1. @SpringBootTest сканирование
    2. @SpringBootApplication (@ComponentScan) Вниз
    Вверх

    View Slide

  187. @SpringBootTest
    Два процесса сканирования

    View Slide

  188. @SpringBootTest
    Два процесса сканирования

    View Slide

  189. @SpringBootTest
    Два процесса сканирования

    View Slide

  190. Два процесса сканирования
    @SpringBootTest

    View Slide

  191. Два процесса сканирования
    @SpringBootTest
    test classpath extends main classpath

    View Slide

  192. View Slide

  193. Два процесса сканирования
    @SpringBootTest
    @SpringBootApplication
    src/main будет так же просканирован*
    test classpath extends main classpath

    View Slide

  194. Два процесса сканирования
    @SpringBootTest
    @SpringBootApplication
    src/main будет так же просканирован*
    test classpath extends main classpath

    View Slide

  195. Два процесса сканирования
    @SpringBootTest
    @SpringBootApplication
    src/main будет так же просканирован*
    test classpath extends main classpath

    View Slide

  196. Два процесса сканирования
    @SpringBootTest
    @SpringBootApplication
    src/main будет так же просканирован*
    test classpath extends main classpath

    View Slide

  197. Два процесса сканирования
    @SpringBootTest
    @SpringBootApplication
    src/main будет так же просканирован*
    test classpath extends main classpath

    View Slide

  198. Тоже и с
    src/main/**

    View Slide

  199. Как чинить
    @SpringBootApplication
    @EnableFeignClients
    @EnableConfigurationProperties(AssistantProperties.class)
    public class DeveloperAssistantApplication {
    public static void main(String[] args) {
    SpringApplication.run(DeveloperAssistantApplication.class, args);
    }
    }

    View Slide

  200. Как чинить
    @SpringBootApplication
    @EnableFeignClients
    @EnableConfigurationProperties(AssistantProperties.class)
    public class DeveloperAssistantApplication {
    public static void main(String[] args) {
    SpringApplication.run(DeveloperAssistantApplication.class, args);
    }
    }

    View Slide

  201. Как чинить
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes =
    AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {

    View Slide

  202. Как чинить
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes =
    AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {

    View Slide

  203. Как чинить
    /**
    * @author Phillip Webb
    * @since 1.4.0
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    }

    View Slide

  204. 1. Запустим все тесты
    2. DeveloperAssistantApplicationTests.contextLoad падает
    3. Загрузил бины из другого теста!
    4. @TestConfiguration!
    5. DeveloperAssistantApplicationTests.contextLoad работает
    6. А AnswerCacheServiceJPABackendTest перестал
    7. Загрузил бины из другого теста!
    8. @SpringBootConfiguration остановит сканирование
    Все ли хорошо?

    View Slide

  205. @SpringBootConfiguration
    public class StopConfiguration {
    }
    В нужном пакете!
    Чиним

    View Slide

  206. @SpringBootConfiguration
    Нужный пакет для остановки

    View Slide

  207. Component Tests

    View Slide

  208. Spring Boot обновки
    1. @SpringBootTest
    2. @TestConfiguration
    3. @MockBean && @SpyBean
    4. @DataJpaTest
    5. @MockMvcTest

    View Slide

  209. @DataJpaTest
    1. сканирует все репозитории

    View Slide

  210. @DataJpaTest
    1. сканирует все репозитории
    2. конфигурирует EntityManager
    3. загружает другие конфигурации

    View Slide

  211. 1. сканирует все репозитории
    2. конфигурирует EntityManager
    3. загружает другие конфигурации
    4. фильтрует все не относящееся к Data/JPA
    Применим знания
    @DataJpaTest

    View Slide

  212. Тестируем DefaultAssistantJpaBackendTest
    1. @DataJpaTest не загружает компоненты Spring*

    View Slide

  213. Тестируем DefaultAssistantJpaBackendTest
    1. @DataJpaTest не загружает компоненты Spring*
    2. Делаем конфигурацию, загружаем недостающее

    View Slide

  214. Тестируем DefaultAssistantJpaBackendTest
    1. @DataJpaTest не загружает компоненты Spring
    2. Делаем конфигурацию, загружаем недостающее
    3. Ничего не работает, из за @SpringBootConfiguration

    View Slide

  215. Тестируем DefaultAssistantJpaBackendTest
    1. @DataJpaTest не загружает компоненты Spring*
    2. Делаем конфигурацию, загружаем недостающее
    3. Ничего не работает, из за @SpringBootConfiguration
    4. Переносим в новый package – все @*Test тесты должны быть
    изолированы

    View Slide

  216. @WebMvcTest
    1. Не грузит компоненты спринга

    View Slide

  217. @WebMvcTest
    1. Не грузит компоненты спринга
    2. Грузит только то что относится к Web

    View Slide

  218. @WebMvcTest
    1. Не грузит компоненты спринга
    2. Грузит только то что относится к Web
    3. Сразу изолируем в отдельный пакет
    Получаем суперспособность:
    @Autowired
    MockMvc mockMvc;

    View Slide

  219. Где настраивать @MockBean
    1. В @*Configuration –
    если мок нужен на этапе создания контекста
    2. В тесте (@Before/setup/etc)
    если мок нужен только на этапе выполнения теста

    View Slide

  220. Что же делает @SpringBootTest
    1. Без classes
    a. сканирует со своего пакета “вверх” в поисках @SpringBootConfiguration
    i. игнорирует остальных
    b. падает если не находит или находит несколько в одном пакете
    2. [email protected]
    a. поднимет только указанные конфигурации
    3. [email protected]ation
    a. поднимет указанный контекст и продолжит сканирование. см пункт 1

    View Slide

  221. Зачем нужен @SpringBootTest
    1. Полный тест на весь контекст
    2. Изменение properties
    3. Тесты с определенным скоупом – пакет/конфигурация/автоскан

    View Slide

  222. Зачем нужен @TestConfiguration
    1. Если нужно не прерывать сканирование @SpringBootTest
    2. Изолированные тесты (игнорируется при сканировании)

    View Slide

  223. Зачем нужен @SpringBootConfiguration
    1. Прерывать сканирование инициированное @SpringBootTest

    View Slide

  224. Есть два типа тестов
    Какой сам выберешь,
    а какой разработчику оставишь?
    Простой Сложный

    View Slide

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

    View Slide

  226. 1. Не боимся залезать в кишки приложения
    2. Spring Boot богат на инструменты для тестирования
    3. Но вносит свои ограничения – структура тестов
    Выводы

    View Slide

  227. Шкала Тестов
    Unit
    Component Test
    Microservice Test
    System Test




    Следующий
    доклад

    View Slide

  228. Unit Component Microservice
    Что нужно Junit/Mockito @ContextConfiguration @SpringBootTest
    Кто управляет new Spring Spring Boot

    View Slide

  229. QA
    229

    View Slide

  230. 1. @ComponentScan > @TestConfiguration > @Configuratin
    ! @ComponentScan находит даже @TestConfiguration
    2. @DataJpaTest > @SpringBootTest
    3. @DataJpaTest и @WebMvcTest должны быть в отдельных пакетах
    Если есть сомнения – смотри автора! Juergen Hoeller*
    Дополнительно

    View Slide

  231. Замечания
    1. Spring для Unit тестирования может быть быстрым

    View Slide

  232. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    Замечания

    View Slide

  233. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    3. Для тестов – только @TestConfiguration
    Замечания

    View Slide

  234. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    3. Для тестов – только @TestConfiguration
    4. Изолировать группы тестов с помощью
    Замечания

    View Slide

  235. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    3. Для тестов – только @TestConfiguration
    4. Изолировать группы тестов с помощью
    a. выделения в пакеты
    b. @SpringBootConfiguration
    Замечания

    View Slide

  236. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    3. Для тестов – только @TestConfiguration
    4. Изолировать группы тестов с помощью
    a. выделения в пакеты (особенно для @*Test)
    b. @SpringBootConfiguration
    5. SpringBootTest надо в основном использовать
    для микросервис тестов
    Замечания

    View Slide

  237. 1. Spring для Unit тестирования может быть быстрым
    2. Кэш контекстов – хрупкая штука
    3. Для тестов – только @TestConfiguration
    4. Изолировать группы тестов с помощью
    a. выделения в пакеты
    b. @SpringBootConfiguration
    5. SpringBootTest надо в основном использовать
    для микросервис тестов
    6. Если есть DirtiesContext – стоит задуматься :)
    Дополнительно

    View Slide

  238. 1. Demo Source with Spring Boot 2.1 and Gradle —
    https://github.com/lavcraft/spring-boot-curse
    2. Old Demo Source with Spring Boot 1.5 and Maven —
    https://github.com/lavcraft/conference-test-with-spring-boot-test
    3. Spring Test Reference Guide
    4. Spring Boot Test Reference Guide
    5. Spring 1.4 Test Improvements
    6. Custom Test Slice with Spring Boot
    Ссылки

    View Slide