mockito
当使用遗留代码时,我不会费心去测试数据结构,这意味着对象只带有getter和setter,映射,列表等。原因之一是我从不模拟它们。 在测试使用它们的类时,我照原样使用它们。 对于构建器,当它们仅由测试类使用时,我也不会对其进行单元测试,因为它们在许多其他测试中均被用作“帮助者”。 如果它们有错误,则测试将失败。 总而言之,如果这些数据结构和构建器已经存在,那么我不会为它们进行改装测试。
但是,现在让我们谈谈进行TDD并假设您需要一个带有getter和setter的新对象。 在这种情况下,是的,我将为吸气剂和吸气剂编写测试,因为我需要先编写测试来证明它们的存在。
为了拥有丰富的领域模型,我通常倾向于将业务逻辑与数据相关联,并拥有更丰富的领域模型。 让我们看下面的例子。
在现实生活中,我会一次编写测试,使它们通过并重构。 在这篇文章中,为清晰起见,我仅向您提供完整的课程。 首先让我们编写测试:
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));
}
}
现在执行:
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的属性,具有相应的getter和setter,并且在isReportable业务方法中还使用了一个协作者(reportabilityDecision,通过setter注入)。
我多次见过的“测试” setReportabilityDecision方法的常见方法是引入getReportabilityDecision方法,该方法返回reportabilityDecision(协作者)对象。
这绝对是错误的方法。 我们的目标应该是测试协作器的使用方式,即是否使用正确的参数调用协作器,以及是否使用返回的任何东西(如果返回任何东西)。 在这种情况下引入吸气剂是没有意义的,因为它不能保证在通过设置器注入了协作者之后,对象将按照我们的预期与协作者进行交互。
顺便说一句,当我们编写有关将如何使用协作者的测试时,定义它们的接口是在将TDD用作设计工具而不仅仅是将其用作测试工具时。 我将在以后的博客文章中进行介绍。
好的,现在假设可以以不同的方式(即具有不同的可报告性决策)创建此贸易对象。 我们还希望使代码更具可读性,并决定为Trade对象编写一个生成器。 在这种情况下,我们还假设我们希望生成器也用于生产和测试代码中。 在这种情况下,我们要测试驱动器。
这是我通常在开发人员测试驱动构建器实现时发现的一个示例。
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没有显示出它的真实意图。 经过多次重构和探索不同的想法之后,在与团队中的一个人进行配对编程时,我们想到了这种方法:
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的期望,即调用build方法时的副作用。 我们希望它创建交易并设置其属性。 TradeTest没有重复项。 它留给TradeTest来保证Trade对象的正确行为。
为了完善起见,这是最后的TradeBuider类:
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的结合非常强大,使我们能够编写更好,更易读的测试。
参考:来自Crafts Software博客的JCG合作伙伴 Sandro Mancuso的Mockito和Hamcrest的测试驱动构建器 。
翻译自: https://www.javacodegeeks.com/2012/06/test-driving-builders-with-mockito-and.html
mockito