当使用遗留代码时,我不会费心去测试数据结构,这意味着对象只带有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;@Beforepublic void initialise() {trade = new Trade();when(reportabilityDecision.isReportable(anyString())).thenReturn(REPORTABILITY_RESULT);}@Test public voidshould_contain_the_inbound_xml_message() {trade.setInboundMessage(INBOUND_XML_MESSAGE);assertThat(trade.getInboundMessage(), is(INBOUND_XML_MESSAGE));}@Test public voidshould_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 >';@Mockprivate ReportabilityDecision reportabilityDecision;@Test public voidshould_create_a_trade_with_inbound_message() {Trade trade = aTrade().withInboundMessage(TRADE_XML_MESSAGE).build();assertThat(trade.getInboundMessage(), is(TRADE_XML_MESSAGE));}@Test public voidshould_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 voidshould_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