Извините за то, что не писал в течение некоторого времени, но я был занят написанием JBoss Drools Refcard для DZone, и я сейчас нахожусь в процессе написания книги о Mockito, так что у меня не осталось слишком много времени для ведения блога…
Во всяком случае, совсем недавно в моем текущем проекте у меня была интересная ситуация с модульным тестированием со структурами Mockito и JAXB. У нас есть очень глубоко вложенные структуры JAXB, сгенерированные из предоставленных нам схем, что означает, что мы не можем их изменить в любом случае.
Давайте посмотрим на структуру проекта:
Структура проекта довольно проста — существует файл схемы Player.xsd, который благодаря использованию плагина jaxb2-maven-plugin создает сгенерированные классы Java JAXB, соответствующие схеме в папке target / jaxb / в соответствующем пакете, который определен в pom.xml . Кстати, давайте посмотрим на файл pom.xml .
Pom.xml:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
< project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" < modelVersion >4.0.0</ modelVersion > < groupId >com.blogspot.toomuchcoding</ groupId > < artifactId >mockito-deep_stubs</ artifactId > < version >0.0.1-SNAPSHOT</ version > < properties > < project.build.sourceEncoding >UTF-8</ project.build.sourceEncoding > < maven.compiler.source >1.6</ maven.compiler.source > < maven.compiler.target >1.6</ maven.compiler.target > </ properties > < repositories > < repository > < id >spring-release</ id > </ repository > < repository > < id >maven-us-nuxeo</ id > </ repository > </ repositories > < dependencies > < dependency > < groupId >junit</ groupId > < artifactId >junit</ artifactId > < version >4.10</ version > </ dependency > < dependency > < groupId >org.mockito</ groupId > < artifactId >mockito-all</ artifactId > < version >1.9.5</ version > < scope >test</ scope > </ dependency > </ dependencies > < build > < pluginManagement > < plugins > < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-compiler-plugin</ artifactId > < version >2.5.1</ version > </ plugin > </ plugins > </ pluginManagement > < plugins > < plugin > < groupId >org.codehaus.mojo</ groupId > < artifactId >jaxb2-maven-plugin</ artifactId > < version >1.5</ version > < executions > < execution > < id >xjc</ id > < goals > < goal >xjc</ goal > </ goals > </ execution > </ executions > < configuration > < packageName >com.blogspot.toomuchcoding.model</ packageName > < schemaDirectory >${project.basedir}/src/main/resources/xsd</ schemaDirectory > </ configuration > </ plugin > </ plugins > </ build > </ project > |
Помимо ранее определенных зависимостей проекта, как упоминалось ранее в модуле jaxb2-maven-plugin в узле конфигурации, вы можете определить значение packageName, которое определяет, какому пакету следует генерировать классы JAXB, на основе значения schemaDirectory, где плагин может найти правильные файлы схемы.
Говоря об этом, давайте проверим файл схемы Player.xsd (аналогичный тому, который был представлен в моей статье по автоматическому преобразованию сообщений Spring JMS ):
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<? xml version = "1.0" encoding = "UTF-8" ?> < xsd:element name = "PlayerDetails" > < xsd:complexType > < xsd:sequence > < xsd:element name = "Name" type = "xsd:string" /> < xsd:element name = "Surname" type = "xsd:string" /> < xsd:element name = "Position" type = "PositionType" /> < xsd:element name = "Age" type = "xsd:int" /> < xsd:element name = "ClubDetails" type = "ClubDetails" /> </ xsd:sequence > </ xsd:complexType > </ xsd:element > < xsd:complexType name = "ClubDetails" > < xsd:sequence > < xsd:element name = "TeamName" type = "xsd:string" /> < xsd:element name = "Country" type = "CountryDetails" /> </ xsd:sequence > </ xsd:complexType > < xsd:complexType name = "CountryDetails" > < xsd:sequence > < xsd:element name = "CountryName" type = "xsd:string" /> < xsd:element name = "CountryCode" type = "CountryCodeDetails" /> </ xsd:sequence > </ xsd:complexType > < xsd:complexType name = "CountryCodeDetails" > < xsd:sequence > < xsd:element name = "CountryName" type = "xsd:string" /> < xsd:element name = "CountryCode" type = "CountryCodeType" /> </ xsd:sequence > </ xsd:complexType > < xsd:simpleType name = "CountryCodeType" > < xsd:restriction base = "xsd:string" > < xsd:enumeration value = "PL" /> < xsd:enumeration value = "GER" /> < xsd:enumeration value = "FRA" /> < xsd:enumeration value = "ENG" /> < xsd:enumeration value = "ESP" /> </ xsd:restriction > </ xsd:simpleType > < xsd:simpleType name = "PositionType" > < xsd:restriction base = "xsd:string" > < xsd:enumeration value = "GK" /> < xsd:enumeration value = "DEF" /> < xsd:enumeration value = "MID" /> < xsd:enumeration value = "ATT" /> </ xsd:restriction > </ xsd:simpleType > </ xsd:schema > |
Как вы видите, я определяю некоторые сложные типы, которые, хотя могут и не иметь делового смысла, но вы можете найти такие примеры в реальной жизни.
Давайте выясним, как выглядит метод, который мы хотели бы проверить. Здесь у нас есть PlayerServiceImpl, который реализует интерфейс PlayerService :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
package com.blogspot.toomuchcoding.service; import com.blogspot.toomuchcoding.model.PlayerDetails; /** * User: mgrzejszczak * Date: 08.06.13 * Time: 19:02 */ public class PlayerServiceImpl implements PlayerService { @Override public boolean isPlayerOfGivenCountry(PlayerDetails playerDetails, String country) { String countryValue = playerDetails.getClubDetails().getCountry().getCountryCode().getCountryCode().value(); return countryValue.equalsIgnoreCase(country); } } |
Мы получаем вложенные элементы из сгенерированных JAXB классов. Хотя он нарушает закон Деметры, довольно часто называют методы структур, потому что сгенерированные JAXB классы на самом деле являются структурами, поэтому на самом деле я полностью согласен с Мартином Фаулером, что его следует называть предложением Деметры . В любом случае, давайте посмотрим, как вы можете проверить метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
@Test public void shouldReturnTrueIfCountryCodeIsTheSame() throws Exception { //given PlayerDetails playerDetails = new PlayerDetails(); ClubDetails clubDetails = new ClubDetails(); CountryDetails countryDetails = new CountryDetails(); CountryCodeDetails countryCodeDetails = new CountryCodeDetails(); playerDetails.setClubDetails(clubDetails); clubDetails.setCountry(countryDetails); countryDetails.setCountryCode(countryCodeDetails); countryCodeDetails.setCountryCode(CountryCodeType.ENG); //when boolean playerOfGivenCountry = objectUnderTest.isPlayerOfGivenCountry(playerDetails, COUNTRY_CODE_ENG); //then assertThat(playerOfGivenCountry, is( true )); } |
Функция проверяет, получите ли вы тот же логический код, если у вас есть тот же код страны. Единственная проблема — количество наборов и экземпляров, которые имеют место, когда вы хотите создать входное сообщение. В наших проектах у нас вдвое больше вложенных элементов, поэтому вы можете только представить количество кода, которое нам нужно будет создать для создания объекта ввода…
Итак, что можно сделать, чтобы улучшить этот код? На помощь приходит Mockito вместе с ответом по умолчанию RETURN_DEEP_STUBS на метод Mockito.mock (…) :
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Test public void shouldReturnTrueIfCountryCodeIsTheSameUsingMockitoReturnDeepStubs() throws Exception { //given PlayerDetails playerDetailsMock = mock(PlayerDetails. class , RETURNS_DEEP_STUBS); CountryCodeType countryCodeType = CountryCodeType.ENG; when(playerDetailsMock.getClubDetails().getCountry().getCountryCode().getCountryCode()).thenReturn(countryCodeType); //when boolean playerOfGivenCountry = objectUnderTest.isPlayerOfGivenCountry(playerDetailsMock, COUNTRY_CODE_ENG); //then assertThat(playerOfGivenCountry, is( true )); } |
Итак, здесь произошло то, что вы используете метод Mockito.mock (…) и предоставили ответ RETURNS_DEEP_STUBS, который автоматически создаст для вас издевательства. Имейте в виду, что Enums нельзя смоделировать , поэтому вы не можете писать в функцию Mockito.when (…) playerDetailsMock.getClubDetails (). GetCountry () . GetCountryCode () . GetCountryCode () . GetValue () .
Подводя итог, вы можете сравнить читаемость обоих тестов и увидеть, насколько понятнее работать со структурами JAXB, используя ответ по умолчанию Mockito RETURNS_DEEP_STUBS .
Естественно, источники для этого примера доступны в BitBucket и GitHub .