Работая с унаследованным кодом, я не стал бы проверять структуры данных, то есть объекты, содержащие только геттеры и сеттеры, карты, списки и т. Д. Одна из причин заключается в том, что я никогда не высмеиваю их. Я использую их как они есть при тестировании классов, которые их используют. Для сборщиков, когда они используются только тестовыми классами, я также не тестирую их модульно, поскольку они используются в качестве «помощников» во многих других тестах. Если у них есть ошибка, тесты не пройдут. Таким образом, если бы эти структуры данных и компоновщики уже существовали, я бы не стал их модернизировать.
Но теперь давайте поговорим о создании TDD и предположим, что вам нужен новый объект с геттерами и сеттерами. В этом случае, да, я бы написал тесты для геттеров и сеттеров, поскольку мне нужно сначала обосновать их существование, написав мои тесты.
Чтобы иметь богатую модель предметной области, я обычно склонен связывать бизнес-логику с данными и иметь более богатую модель предметной области. Давайте посмотрим на следующий пример.
В реальной жизни я бы писал на тесте за раз, заставляя их проходить и рефакторинг. Для этого поста я просто дам вам полные уроки для ясности. Сначала напишем тесты:
|
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
|
package org.craftedsw.testingbuilders;import static org.hamcrest.Matchers.is;import static org.junit.Assert.assertThat;import static org.mockito.Matchers.anyString;import static org.mockito.Mockito.verify;import static org.mockito.Mockito.when;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.mockito.Mock;import org.mockito.runners.MockitoJUnitRunner;@RunWith(MockitoJUnitRunner.class)public class TradeTest { private static final String INBOUND_XML_MESSAGE = '<message >'; private static final boolean REPORTABILITY_RESULT = true; private Trade trade; @Mock private ReportabilityDecision reportabilityDecision; @Before public void initialise() { trade = new Trade(); when(reportabilityDecision.isReportable(anyString())) .thenReturn(REPORTABILITY_RESULT); } @Test public void should_contain_the_inbound_xml_message() { trade.setInboundMessage(INBOUND_XML_MESSAGE); assertThat(trade.getInboundMessage(), is(INBOUND_XML_MESSAGE)); } @Test public void should_tell_if_it_is_reportable() { trade.setInboundMessage(INBOUND_XML_MESSAGE); trade.setReportabilityDecision(reportabilityDecision); boolean reportable = trade.isReportable(); verify(reportabilityDecision).isReportable(INBOUND_XML_MESSAGE); assertThat(reportable, is(REPORTABILITY_RESULT)); }} |
Теперь реализация:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package org.craftedsw.testingbuilders;public class Trade { private String inboundMessage; private ReportabilityDecision reportabilityDecision; public String getInboundMessage() { return this.inboundMessage; } public void setInboundMessage(String inboundXmlMessage) { this.inboundMessage = inboundXmlMessage; } public boolean isReportable() { return reportabilityDecision.isReportable(inboundMessage); } public void setReportabilityDecision(ReportabilityDecision reportabilityDecision) { this.reportabilityDecision = reportabilityDecision; }} |
Этот случай интересен, поскольку у объекта Trade есть одно свойство, называемое inboundMessage, с соответствующими получателями и установщиками, а также использует коллаборатор (reportabilityDecision, введенный через установщик) в своем бизнес-методе isReportable.
Обычный подход, который я видел много раз для «тестирования» метода setReportabilityDecision, заключается во введении метода getReportabilityDecision, возвращающего объект reportabilityDecision (сотрудник).
Это определенно неправильный подход. Наша цель должна состоять в том, чтобы проверить, как используется коллаборатор, то есть, если он вызывается с правильными параметрами и используется ли все, что он возвращает (если он возвращает что-либо). Представлять геттер в этом случае не имеет смысла, так как он не гарантирует, что объект, после того, как соавтор введен через сеттер, взаимодействует с коллаборатором, как мы и предполагали.
Кроме того, когда мы пишем тесты о том, как будут использоваться коллаборационисты, определяя их интерфейс, это когда мы используем TDD как инструмент проектирования, а не просто как инструмент тестирования. Я расскажу об этом в следующем посте.
Хорошо, теперь представьте, что этот торговый объект может быть создан по-разному, то есть, с разными решениями по отчетности. Мы также хотели бы сделать наш код более читабельным и решили написать конструктор для объекта Trade. Давайте также предположим, что в этом случае мы хотим, чтобы конструктор также использовался в рабочем и тестовом коде. В этом случае мы хотим протестировать наш строитель.
Вот пример, который я обычно нахожу, когда разработчики тестируют реализацию сборки.
|
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
|
package org.craftedsw.testingbuilders;import static org.craftedsw.testingbuilders.TradeBuilder.aTrade;import static org.hamcrest.Matchers.is;import static org.junit.Assert.assertThat;import static org.mockito.Mockito.verify;import org.junit.Test;import org.junit.runner.RunWith;import org.mockito.Mock;import org.mockito.runners.MockitoJUnitRunner;@RunWith(MockitoJUnitRunner.class)public class TradeBuilderTest { private static final String TRADE_XML_MESSAGE = '<message >'; @Mock private ReportabilityDecision reportabilityDecision; @Test public void should_create_a_trade_with_inbound_message() { Trade trade = aTrade() .withInboundMessage(TRADE_XML_MESSAGE) .build(); assertThat(trade.getInboundMessage(), is(TRADE_XML_MESSAGE)); } @Test public void should_create_a_trade_with_a_reportability_decision() { Trade trade = aTrade() .withInboundMessage(TRADE_XML_MESSAGE) .withReportabilityDecision(reportabilityDecision) .build(); trade.isReportable(); verify(reportabilityDecision).isReportable(TRADE_XML_MESSAGE); }} |
Теперь давайте посмотрим на эти тесты. Хорошая новость в том, что тесты были написаны так, как разработчики хотят их прочитать. Это также означает, что они «проектировали» открытый интерфейс TradeBuilder (публичные методы). Плохая новость в том, как они это проверяют.
Если вы посмотрите ближе, тесты для компоновщика практически идентичны тестам в классе TradeTest.
Вы можете сказать, что все в порядке, поскольку создатель создает объект и тесты должны быть похожими. Единственное отличие состоит в том, что в TradeTest мы создаем экземпляр объекта вручную, а в TradeBuilderTest мы используем его создатель, но утверждения должны быть одинаковыми, верно?
Для меня, во-первых, у нас есть дублирование. Во-вторых, TradeBuilderTest не показывает его истинное намерение.
После многих рефакторингов и изучения различных идей во время парного программирования с одним из парней из моей команды мы разработали такой подход:
|
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
|
package org.craftedsw.testingbuilders;import static org.mockito.BDDMockito.given;import static org.mockito.Mockito.verify;import org.junit.Test;import org.junit.runner.RunWith;import org.mockito.InjectMocks;import org.mockito.Mock;import org.mockito.Spy;import org.mockito.runners.MockitoJUnitRunner;@RunWith(MockitoJUnitRunner.class)public class TradeBuilderTest { private static final String TRADE_XML_MESSAGE = '<message >'; @Mock private ReportabilityDecision reportabilityDecision; @Mock private Trade trade; @Spy @InjectMocks TradeBuilder tradeBuilder; @Test public void should_create_a_trade_with_all_specified_attributes() { given(tradeBuilder.createTrade()).willReturn(trade); tradeBuilder .withInboundMessage(TRADE_XML_MESSAGE) .withReportabilityDecision(reportabilityDecision) .build(); verify(trade).setInboundMessage(TRADE_XML_MESSAGE); verify(trade).setReportabilityDecision(reportabilityDecision); }} |
Итак, теперь TradeBuilderTest выражает то, что ожидается от TradeBuilder, то есть побочный эффект при вызове метода сборки. Мы хотим, чтобы он создал сделку и установил ее атрибуты. Там нет дубликатов с TradeTest. TradeTest оставляет за собой право гарантировать правильное поведение объекта Trade.
Для завершения, вот последний класс TradeBuider:
|
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
|
package org.craftedsw.testingbuilders;public class TradeBuilder { private String inboundMessage; private ReportabilityDecision reportabilityDecision; public static TradeBuilder aTrade() { return new TradeBuilder(); } public TradeBuilder withInboundMessage(String inboundMessage) { this.inboundMessage = inboundMessage; return this; } public TradeBuilder withReportabilityDecision(ReportabilityDecision reportabilityDecision) { this.reportabilityDecision = reportabilityDecision; return this; } public Trade build() { Trade trade = createTrade(); trade.setInboundMessage(inboundMessage); trade.setReportabilityDecision(reportabilityDecision); return trade; } Trade createTrade() { return new Trade(); }} |
Сочетание Mockito и Hamcrest чрезвычайно мощно, что позволяет нам писать более качественные и удобочитаемые тесты.
Ссылка: тест-драйв Builders с Mockito и Hamcrest от нашего партнера JCG Сандро Манкузо в блоге Crafted Software .