Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

@tolkv @lavcraft

Slide 3

Slide 3 text

@jekaborisov @jeka1978

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Начнём

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Эксперты

Slide 27

Slide 27 text

Эксперты

Slide 28

Slide 28 text

Demo

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Demo JokerWordsFrequencyResolverTest

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Demo

Slide 38

Slide 38 text

Кого тестируем @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 text

Тест №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 text

Тест №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 text

Тест №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 text

Тест №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 text

Тест №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 text

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

Slide 45

Slide 45 text

Not Passed

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Demo

Slide 77

Slide 77 text

Тест №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 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

Тест №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 text

Тест №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 text

Тест №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 text

Passed

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

/** * {@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

Slide 88

Slide 88 text

SpringExtension — Junit5 @SpringJUnitConfig @SpringJUnitWebConfig

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

Demo TextBasedQuestionTypeResolverTest

Slide 93

Slide 93 text

@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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

@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") ?

Slide 97

Slide 97 text

@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") ?

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

Not Passed

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

Что случилось 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 text

@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

Slide 104

Slide 104 text

@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

Slide 105

Slide 105 text

Not Passed

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

Demo

Slide 111

Slide 111 text

@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

Slide 112

Slide 112 text

@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

Slide 113

Slide 113 text

Not Passed

Slide 114

Slide 114 text

@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

Slide 115

Slide 115 text

Passed

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

Only once … only once … only once

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

Demo

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

No content

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

Not Passed

Slide 133

Slide 133 text

No content

Slide 134

Slide 134 text

Не найден spring bean WordsComposer

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

Passed

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

Сделали круг

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

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

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

Demo AnswerCacheServiceJPABackend

Slide 156

Slide 156 text

Что тестируем @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 text

Что тестируем @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 text

Что тестируем @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 text

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

Slide 160

Slide 160 text

Как тестируем @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 text

Как тестируем @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 text

Как тестируем @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 text

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

Slide 164

Slide 164 text

Как тестируем – сам тест @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

Slide 165

Slide 165 text

Passed

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

Not Passed

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

No content

Slide 175

Slide 175 text

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

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

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

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

Spring Заговор

Slide 183

Slide 183 text

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

Slide 184

Slide 184 text

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

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

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

Slide 187

Slide 187 text

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

Slide 188

Slide 188 text

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

Slide 189

Slide 189 text

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

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

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

Slide 192

Slide 192 text

No content

Slide 193

Slide 193 text

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

Slide 194

Slide 194 text

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

Slide 195

Slide 195 text

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

Slide 196

Slide 196 text

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

Slide 197

Slide 197 text

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

Slide 198

Slide 198 text

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

Slide 199

Slide 199 text

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

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

Как чинить @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 text

Как чинить @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 text

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

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

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

Slide 206

Slide 206 text

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

Slide 207

Slide 207 text

Component Tests

Slide 208

Slide 208 text

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

Slide 209

Slide 209 text

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

Slide 210

Slide 210 text

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

Slide 211

Slide 211 text

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

Slide 212

Slide 212 text

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

Slide 213

Slide 213 text

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

Slide 214

Slide 214 text

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

Slide 215

Slide 215 text

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

Slide 216

Slide 216 text

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

Slide 217

Slide 217 text

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

Slide 218

Slide 218 text

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

Slide 219

Slide 219 text

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

Slide 220

Slide 220 text

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

Slide 221

Slide 221 text

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

Slide 222

Slide 222 text

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

Slide 223

Slide 223 text

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

Slide 224

Slide 224 text

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

Slide 225

Slide 225 text

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

Slide 226

Slide 226 text

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

Slide 227

Slide 227 text

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

Slide 228

Slide 228 text

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

Slide 229

Slide 229 text

QA 229

Slide 230

Slide 230 text

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

Slide 231

Slide 231 text

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

Slide 232

Slide 232 text

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

Slide 233

Slide 233 text

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

Slide 234

Slide 234 text

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

Slide 235

Slide 235 text

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

Slide 236

Slide 236 text

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

Slide 237

Slide 237 text

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

Slide 238

Slide 238 text

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 Ссылки