Я работаю с весной несколько лет. Но я всегда был разочарован тем, насколько грязной может стать конфигурация XML. По мере появления различных аннотаций и возможностей настройки Java я начал получать удовольствие от программирования на Spring. Вот почему я настоятельно рекомендую использовать конфигурацию Java. На мой взгляд, конфигурация XML подходит только тогда, когда вам нужно визуализировать Spring Integration или Spring Batch. Надеемся, что Spring Tool Suite также сможет визуализировать конфигурации Java для этих платформ.
Одним из неприятных аспектов конфигурации XML является то, что он часто приводит к огромным файлам конфигурации XML. Поэтому разработчики часто создают конфигурацию тестового контекста для интеграционного тестирования. Но какова цель интеграционного тестирования, когда не тестируется производственная проводка? Такой интеграционный тест имеет очень мало значения. Поэтому я всегда пытался спроектировать свои производственные контексты тестируемым способом.
Я исключаю, что когда вы создаете новый проект / модуль, вы максимально избегаете настройки XML. Таким образом, с помощью конфигурации Java вы можете создать конфигурацию Spring для модуля / пакета и сканировать их в основном контексте (@Configuration также является кандидатом для сканирования компонентов). Таким образом, вы можете естественным образом создавать острова Весенние бобы. Эти острова могут быть легко проверены в изоляции.
Но я должен признать, что не всегда возможно протестировать производственную конфигурацию Java как есть. Редко вам нужно изменить поведение или шпионить за определенными бобами. Для этого есть библиотека под названием Springockito . Честно говоря, я не использовал его до сих пор, потому что я всегда стараюсь проектировать конфигурацию Spring, чтобы избежать надругательства. Глядя на темпы развития Springockito и количество открытых вопросов , я бы немного беспокоился о том, чтобы внедрить его в свой набор тестов. Тот факт, что последний выпуск был сделан до выпуска Spring 4, поднимает такие вопросы, как «Возможно ли легко интегрировать его с Spring 4?». Я не знаю, потому что я не пробовал это. Я предпочитаю чистый подход Spring, если мне нужно смоделировать Spring bean в интеграционном тесте.
Spring предоставляет аннотацию @Primary
для указания того, какой компонент должен быть предпочтительным в случае, когда зарегистрированы два компонента с одинаковым типом. Это удобно, поскольку в интеграционном тесте вы можете переопределить производственный компонент с помощью поддельного компонента. Давайте рассмотрим этот подход и некоторые подводные камни на примерах.
Я выбрал эту упрощенную / фиктивную структуру производственного кода для демонстрации:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Repository public class AddressDao { public String readAddress(String userName) { return "3 Dark Corner" ; } } @Service public class AddressService { private AddressDao addressDao; @Autowired public AddressService(AddressDao addressDao) { this .addressDao = addressDao; } public String getAddressForUser(String userName){ return addressDao.readAddress(userName); } } @Service public class UserService { private AddressService addressService; @Autowired public UserService(AddressService addressService) { this .addressService = addressService; } public String getUserDetails(String userName){ String address = addressService.getAddressForUser(userName); return String.format( "User %s, %s" , userName, address); } } |
Экземпляр одноэлементного компонента AddressService
внедряется в AddressService
. AddressService
аналогичным образом используется в UserService
.
Я должен предупредить вас на этом этапе. Мой подход немного инвазивен для производственного кода. Чтобы иметь возможность подделывать существующие производственные компоненты, мы должны зарегистрировать поддельные компоненты в интеграционном тесте. Но эти поддельные бины обычно находятся в том же поддереве пакета, что и производственные бины (при условии, что вы используете стандартную структуру файлов Maven: «src / main / java» и «src / test / java»). Поэтому, когда они находятся в одном поддереве пакета, они будут сканироваться во время интеграционных тестов. Но мы не хотим использовать все фальшивые бины во всех интеграционных тестах. Подделки могут сломать несвязанные интеграционные тесты. Таким образом, нам нужен механизм, позволяющий тесту использовать только определенные ложные бины. Это делается путем полного исключения поддельных компонентов из сканирования компонентов. Интеграционный тест явно определяет, какие подделки используются (покажет это позже). Теперь давайте рассмотрим механизм исключения поддельных бинов из компонентного сканирования. Мы определяем нашу собственную маркерную аннотацию:
1
2
|
public @interface BeanMock { } |
И исключить аннотацию @BeanMock
из сканирования компонентов в основной конфигурации Spring.
1
2
3
4
5
|
@Configuration @ComponentScan (excludeFilters = @Filter (BeanMock. class )) @EnableAutoConfiguration public class Application { } |
Корневой пакет сканирования компонентов является текущим пакетом класса Application
. Таким образом, все вышеупомянутые производственные бобы должны быть в одной или нескольких упаковках. Теперь нам нужно создать интеграционный тест для UserService
. Давайте шпионим за адресным бином. Конечно, такое тестирование не имеет практического смысла с этим рабочим кодом, но это всего лишь пример. Итак, вот наш шпионский боб:
1
2
3
4
5
6
7
8
9
|
@Configuration @BeanMock public class AddressServiceSpy { @Bean @Primary public AddressService registerAddressServiceSpy(AddressService addressService) { return spy(addressService); } } |
AddressService
компонент AddressService автоматически подключается из производственного контекста, помещается в шпион Mockito и регистрируется в качестве основного компонента для типа AddressService
. @Primary
аннотация гарантирует, что наш поддельный компонент будет использоваться в интеграционном тесте вместо рабочего компонента. Аннотация @BeanMock
гарантирует, что этот компонент не может быть отсканирован сканированием компонента Application
. Давайте посмотрим на интеграционный тест сейчас:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@RunWith (SpringJUnit4ClassRunner. class ) @SpringApplicationConfiguration (classes = { Application. class , AddressServiceSpy. class }) public class UserServiceITest { @Autowired private UserService userService; @Autowired private AddressService addressService; @Test public void testGetUserDetails() { // GIVEN - spring context defined by Application class // WHEN String actualUserDetails = userService.getUserDetails( "john" ); // THEN Assert.assertEquals( "User john, 3 Dark Corner" , actualUserDetails); verify(addressService, times( 1 )).getAddressForUser( "john" ); } } |
Аннотация @SpringApplicationConfigration
имеет два параметра. First ( Application.class
) объявляет тестируемую конфигурацию Spring. Второй параметр ( AddressServiceSpy.class
) указывает AddressServiceSpy.class
компонент, который будет загружен для нашего тестирования в контейнер Spring IoC. Очевидно, что мы можем использовать столько бобовых подделок, сколько необходимо, но вы не хотите иметь много бобовых подделок. Этот подход следует использовать редко, и если вы наблюдаете, как часто используете подобные насмешки, у вас, вероятно, есть серьезные проблемы с жесткой связью в вашем приложении или в вашей группе разработчиков в целом. Методология TDD должна помочь вам решить эту проблему. Имейте в виду: «Меньше дразнить всегда лучше!». Поэтому рассмотрим изменения в дизайне производства, которые позволяют снизить использование макетов. Это касается и модульного тестирования.
В рамках интеграционного теста мы можем автоматически связать этот шпионский компонент и использовать его для различных проверок. В этом случае мы userService.getUserDetails
метод тестирования userService.getUserDetails
вызывал метод addressService.getAddressForUser
с параметром «john».
У меня есть еще один пример. В этом случае мы не будем шпионить за производственным бобом. Мы будем издеваться над этим:
1
2
3
4
5
6
7
8
9
|
@Configuration @BeanMock public class AddressDaoMock { @Bean @Primary public AddressDao registerAddressDaoMock() { return mock(AddressDao. class ); } } |
Мы снова переопределяем производственный компонент, но на этот раз мы заменяем его на макет Mockito . Мы можем записать поведение для макета в нашем интеграционном тесте:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@RunWith (SpringJUnit4ClassRunner. class ) @SpringApplicationConfiguration (classes = { Application. class , AddressDaoMock. class }) public class AddressServiceITest { @Autowired private AddressService addressService; @Autowired private AddressDao addressDao; @Test public void testGetAddressForUser() { // GIVEN when(addressDao.readAddress( "john" )).thenReturn( "5 Bright Corner" ); // WHEN String actualAddress = addressService.getAddressForUser( "john" ); // THEN Assert.assertEquals( "5 Bright Corner" , actualAddress); } @After public void resetMock() { reset(addressDao); } } |
Мы загружаем @SpringApplicationConfiguration
боб через параметр @SpringApplicationConfiguration
. В тестовом методе мы используем метод addressDao.readAddress
для возврата строки «5 Bright Corner», когда ему передается «john» в качестве параметра.
Но имейте в виду, что записанное поведение можно переносить в различные интеграционные тесты через контекст Spring. Мы не хотим, чтобы тесты влияли друг на друга. Таким образом, вы можете избежать будущих проблем в своем наборе тестов, сбрасывая макеты после теста. Это делается в методе resetMock
.
Ссылка: | Как издеваться над бобом Spring без Springockito от нашего партнера JCG, Любоса Крнаца, в |