Curse Of Spring Boot Test [version for QA]

A presentation at Heisenbug in December 2018 in Moscow, Russia by Кирилл Толкачёв

Slide 1

Slide 1

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

Slide 2

Slide 2

@tolkv @lavcraft

Slide 3

Slide 3

@jekaborisov @jeka1978

Slide 4

Slide 4

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

Slide 5

Slide 5

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

Slide 6

Slide 6

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

Slide 7

Slide 7

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

Slide 8

Slide 8

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

Slide 9

Slide 9

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

Slide 10

Slide 10

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

Slide 11

Slide 11

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

Slide 12

Slide 12

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

Slide 13

Slide 13

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

Slide 14

Slide 14

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

Slide 15

Slide 15

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

Slide 16

Slide 16

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

  1. Требование заказчика

Slide 17

Slide 17

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

  1. Требование заказчика 2. Культура

Slide 18

Slide 18

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

  1. Требование заказчика 2. Культура Перед кодом

Slide 19

Slide 19

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

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

Slide 20

Slide 20

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

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

Slide 21

Slide 21

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

Slide 22

Slide 22

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

Slide 23

Slide 23

Начнём

Slide 24

Slide 24

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

Slide 25

Slide 25

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

Slide 26

Slide 26

Эксперты

Slide 27

Slide 27

Эксперты

Slide 28

Slide 28

Demo

Slide 29

Slide 29

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

Slide 30

Slide 30

Slide 31

Slide 31

Slide 32

Slide 32

Slide 33

Slide 33

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

Slide 34

Slide 34

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

Slide 35

Slide 35

Demo JokerWordsFrequencyResolverTest

Slide 36

Slide 36

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

Slide 37

Slide 37

Demo

Slide 38

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

Slide 39

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

Slide 40

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

Slide 41

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

Slide 42

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

Slide 43

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

Slide 44

Slide 44

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

Slide 45

Slide 45

Not Passed

Slide 46

Slide 46

WordsComposer:48 garbageProperties.getGarbage() .contains(s.toLowerCase()) Nu ll Po in te rE xc ep ti on

Slide 47

Slide 47

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

Slide 48

Slide 48

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

Slide 49

Slide 49

➯ Шкала Тестов Unit

Slide 50

Slide 50

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

Slide 51

Slide 51

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

Slide 52

Slide 52

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

Slide 53

Slide 53

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

Slide 54

Slide 54

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

Slide 55

Slide 55

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

Slide 56

Slide 56

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

Slide 57

Slide 57

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

Slide 58

Slide 58

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

Slide 59

Slide 59

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

Slide 60

Slide 60

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

Slide 61

Slide 61

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

Slide 62

Slide 62

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

Slide 63

Slide 63

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

Slide 64

Slide 64

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

Slide 65

Slide 65

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

Slide 66

Slide 66

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

Slide 67

Slide 67

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

Slide 68

Slide 68

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

Slide 69

Slide 69

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

Slide 70

Slide 70

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

Slide 71

Slide 71

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

Slide 72

Slide 72

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

Slide 73

Slide 73

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

Slide 74

Slide 74

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

Slide 75

Slide 75

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

Slide 76

Slide 76

Demo

Slide 77

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

Slide 78

Slide 78

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

Slide 79

Slide 79

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

Slide 80

Slide 80

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

Slide 81

Slide 81

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

Slide 82

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

Slide 83

Slide 83

Passed

Slide 84

Slide 84

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

Slide 85

Slide 85

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

Slide 86

Slide 86

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

Slide 87

Slide 87

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

Slide 88

Slide 88

SpringExtension — Junit5 @SpringJUnitConfig @SpringJUnitWebConfig

Slide 89

Slide 89

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

Slide 90

Slide 90

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

Slide 91

Slide 91

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

Slide 92

Slide 92

Demo TextBasedQuestionTypeResolverTest

Slide 93

Slide 93

Тест #2 @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)); } }

Slide 94

Slide 94

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

Slide 95

Slide 95

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

Slide 96

Slide 96

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

Slide 97

Slide 97

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

Slide 98

Slide 98

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

Slide 99

Slide 99

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

Slide 100

Slide 100

Not Passed

Slide 101

Slide 101

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

Slide 102

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 считываем

Slide 103

Slide 103

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

Slide 104

Slide 104

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

Slide 105

Slide 105

Not Passed

Slide 106

Slide 106

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

Slide 107

Slide 107

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

Slide 108

Slide 108

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

Slide 109

Slide 109

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

Slide 110

Slide 110

Demo

Slide 111

Slide 111

Тест #2 @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)); } }

Slide 112

Slide 112

Тест #2 @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)); } }

Slide 113

Slide 113

Not Passed

Slide 114

Slide 114

Тест #2 @RunWith(SpringRunner. class) @SpringBootTest @ActiveProfiles ("joker_vs_jbaruch") application-joker_vs_jbaruch.yml 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)); } }

Slide 115

Slide 115

Passed

Slide 116

Slide 116

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

Slide 117

Slide 117

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

Slide 118

Slide 118

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

Slide 119

Slide 119

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

Slide 120

Slide 120

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

Slide 121

Slide 121

Only once … only once … only once

Slide 122

Slide 122

Only once … only once … only once Дважды...

Slide 123

Slide 123

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

Slide 124

Slide 124

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

Slide 125

Slide 125

Demo

Slide 126

Slide 126

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

Slide 127

Slide 127

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

Slide 128

Slide 128

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

Slide 129

Slide 129

Slide 130

Slide 130

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

Slide 131

Slide 131

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

Slide 132

Slide 132

Not Passed

Slide 133

Slide 133

Slide 134

Slide 134

Не найден spring bean WordsComposer

Slide 135

Slide 135

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

Slide 136

Slide 136

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

Slide 137

Slide 137

Passed

Slide 138

Slide 138

Only once … only once … only once Дважды...

Slide 139

Slide 139

Сделали круг

Slide 140

Slide 140

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

Slide 141

Slide 141

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

Slide 142

Slide 142

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

Slide 143

Slide 143

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

Slide 144

Slide 144

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

Slide 145

Slide 145

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

Slide 146

Slide 146

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

Slide 147

Slide 147

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

Slide 148

Slide 148

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

Slide 149

Slide 149

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

Slide 150

Slide 150

Only once … only once Один!...

Slide 151

Slide 151

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

Slide 152

Slide 152

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

Slide 153

Slide 153

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

Slide 154

Slide 154

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

Slide 155

Slide 155

Demo AnswerCacheServiceJPABackend

Slide 156

Slide 156

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

Slide 157

Slide 157

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

Slide 158

Slide 158

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

Slide 159

Slide 159

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

Slide 160

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 … } }

Slide 161

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 … } }

Slide 162

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 … } }

Slide 163

Slide 163

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

Slide 164

Slide 164

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

Slide 165

Slide 165

Passed

Slide 166

Slide 166

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

Slide 167

Slide 167

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

Slide 168

Slide 168

Not Passed

Slide 169

Slide 169

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

Slide 170

Slide 170

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

Slide 171

Slide 171

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

Slide 172

Slide 172

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

Slide 173

Slide 173

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

Slide 174

Slide 174

Slide 175

Slide 175

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

Slide 176

Slide 176

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

Slide 177

Slide 177

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

Slide 178

Slide 178

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

Slide 179

Slide 179

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

Slide 180

Slide 180

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

Slide 181

Slide 181

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

Slide 182

Slide 182

Spring Заговор

Slide 183

Slide 183

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

Slide 184

Slide 184

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

Slide 185

Slide 185

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

Slide 186

Slide 186

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

Slide 187

Slide 187

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

Slide 188

Slide 188

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

Slide 189

Slide 189

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

Slide 190

Slide 190

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

Slide 191

Slide 191

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

Slide 192

Slide 192

Slide 193

Slide 193

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

Slide 194

Slide 194

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

Slide 195

Slide 195

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

Slide 196

Slide 196

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

Slide 197

Slide 197

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

Slide 198

Slide 198

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

Slide 199

Slide 199

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

Slide 200

Slide 200

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

Slide 201

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 {

Slide 202

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 {

Slide 203

Slide 203

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

Slide 204

Slide 204

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

Slide 205

Slide 205

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

Slide 206

Slide 206

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

Slide 207

Slide 207

Component Tests

Slide 208

Slide 208

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

Slide 209

Slide 209

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

Slide 210

Slide 210

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

Slide 211

Slide 211

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

Slide 212

Slide 212

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

Slide 213

Slide 213

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

Slide 214

Slide 214

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

Slide 215

Slide 215

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

Slide 216

Slide 216

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

Slide 217

Slide 217

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

Slide 218

Slide 218

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

Slide 219

Slide 219

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

Slide 220

Slide 220

Что же делает @SpringBootTest 1. Без classes a. b. 2. classes=~@Configuration a. 3. сканирует со своего пакета “вверх” в поисках @SpringBootConfiguration i. игнорирует остальных падает если не находит или находит несколько в одном пакете поднимет только указанные конфигурации classes=~@TestConfiguration a. поднимет указанный контекст и продолжит сканирование. см пункт 1

Slide 221

Slide 221

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

Slide 222

Slide 222

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

Slide 223

Slide 223

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

Slide 224

Slide 224

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

Slide 225

Slide 225

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

Slide 226

Slide 226

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

Slide 227

Slide 227

Unit Component Test Microservice Test System Test ➯ ➯ ➯ ➯ Шкала Тестов Следующий доклад

Slide 228

Slide 228

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

Slide 229

Slide 229

QA 229

Slide 230

Slide 230

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

Slide 231

Slide 231

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

Slide 232

Slide 232

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

Slide 233

Slide 233

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

Slide 234

Slide 234

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

Slide 235

Slide 235

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

Slide 236

Slide 236

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

Slide 237

Slide 237

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

Slide 238

Slide 238

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