我遇到的一件事是使用模拟框架的团队假设他们在模拟。
他们并不知道Mocks只是Gerard Meszaros在xunitpatterns.com上归类的“测试双打”之一。
重要的是要意识到每种类型的测试双精度在测试中都扮演着不同的角色。 用与您需要学习不同模式或重构的方式相同,您需要了解每种类型的测试double的原始角色。 然后可以将它们组合起来以满足您的测试需求。
我将简要介绍这种分类的产生方式以及每种类型的不同之处。
我将在Mockito中使用一些简短的简单示例进行此操作。
非常简短的历史
多年来,人们一直在编写系统组件的轻量级版本以帮助进行测试。 通常将其称为存根。 在2000年的文章“ Endo-Testing:使用模拟对象进行单元测试”中介绍了模拟对象的概念。 从那时起,Meszaros将存根,模拟和其他许多类型的测试对象归类为“测试双打”。
该术语已由Martin Fowler在“ Mocks Are n't Stubs”中引用,并在Microsoft社区中被采用,如“ Exploring The Test Doubles Continuum of Test Doubles”中所示。
参考部分中显示了指向这些重要论文的链接。
考试双打的类别
上图显示了常用的双重测试类型。 以下URL提供了对每个模式及其功能以及替代术语的很好的交叉引用。
http://xunitpatterns.com/Test%20Double.html
莫基托
Mockito是一个测试间谍框架,学习起来非常简单。 Mockito值得注意的是,在测试之前没有定义任何模拟对象的期望,因为它们有时在其他模拟框架中也是如此。 开始嘲笑时,这会导致更自然的样式(IMHO)。
以下示例在此处纯粹是为了简单演示如何使用Mockito实施不同类型的测试双打。
网站上有大量有关如何使用Mockito的特定示例。
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html
使用Mockito测试双打
以下是一些使用Mockito的基本示例,以显示Meszaros定义的每个测试双打的作用。
我为每个对象都提供了指向主要定义的链接,因此您可以获得更多示例和完整定义。
虚拟对象
http://xunitpatterns.com/Dummy%20Object.html
这是所有测试双打中最简单的一次。 这是一个没有实现的对象,仅用于填充与测试无关的方法调用的参数。
例如,下面的代码使用很多代码来创建客户,这对测试并不重要。
只要客户数恢复为1,该测试就不会在乎添加哪个客户。
public Customer createDummyCustomer() {County county = new County('Essex');City city = new City('Romford', county);Address address = new Address('1234 Bank Street', city);Customer customer = new Customer('john', 'dobie', address);return customer;
}@Test
public void addCustomerTest() {Customer dummy = createDummyCustomer();AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);assertEquals(1, addressBook.getNumberOfCustomers());
}
我们实际上并不关心客户对象的内容,但是它是必需的。 我们可以尝试使用null值,但是如果代码正确,则可能会引发某种异常。
@Test(expected=Exception.class)
public void addNullCustomerTest() {Customer dummy = null;AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);
}
为了避免这种情况,我们可以使用一个简单的Mockito假人来获得所需的行为。
@Test
public void addCustomerWithDummyTest() {Customer dummy = mock(Customer.class);AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);Assert.assertEquals(1, addressBook.getNumberOfCustomers());
}
正是这个简单的代码创建了一个要传递给调用的虚拟对象。
Customer dummy = mock(Customer.class);
不要被模拟语法所迷惑-这里扮演的角色是虚拟的,而不是模拟的。
区别在于测试双重性的作用,而不是用于创建双重性的语法。
该类可以轻松替代客户类,并使测试非常容易阅读。
测试存根
http://xunitpatterns.com/Test%20Stub.html
测试存根的作用是将受控值返回到要测试的对象。 这些被描述为测试的间接输入。 希望有一个例子可以阐明这意味着什么。
采取以下代码
public class SimplePricingService implements PricingService
{ PricingRepository repository;public SimplePricingService(PricingRepository pricingRepository) {this.repository = pricingRepository;}@Overridepublic Price priceTrade(Trade trade) {return repository.getPriceForTrade(trade);}@Overridepublic Price getTotalPriceForTrades(Collectiontrades) {Price totalPrice = new Price();for (Trade trade : trades){Price tradePrice = repository.getPriceForTrade(trade);totalPrice = totalPrice.add(tradePrice);}return totalPrice;}
SimplePricingService具有一个协作对象,即交易存储库。 交易存储库通过getPriceForTrade方法将交易价格提供给定价服务。
为了测试SimplePricingService中的业务逻辑,我们需要控制这些间接输入
即我们从未通过测试的输入。 如下所示。
在以下示例中,我们对PricingRepository存根以返回可用于测试SimpleTradeService的业务逻辑的已知值。
@Test
public void testGetHighestPricedTrade() throws Exception {Price price1 = new Price(10); Price price2 = new Price(15);Price price3 = new Price(25);PricingRepository pricingRepository = mock(PricingRepository.class);when(pricingRepository.getPriceForTrade(any(Trade.class))).thenReturn(price1, price2, price3);PricingService service = new SimplePricingService(pricingRepository);Price highestPrice = service.getHighestPricedTrade(getTrades());assertEquals(price3.getAmount(), highestPrice.getAmount());
}
破坏者的例子
测试存根有两种常见的变体:响应者和破坏者。
如前面的示例,使用响应者来测试幸福的道路。
破坏者用于测试以下异常行为。
@Test(expected=TradeNotFoundException.class)
public void testInvalidTrade() throws Exception {Trade trade = new FixtureHelper().getTrade();TradeRepository tradeRepository = mock(TradeRepository.class);when(tradeRepository.getTradeById(anyLong())).thenThrow(new TradeNotFoundException());TradingService tradingService = new SimpleTradingService(tradeRepository);tradingService.getTradeById(trade.getId());
}
模拟对象
http://xunitpatterns.com/Mock%20Object.html
模拟对象用于在测试期间验证对象行为。 通过对象行为,我的意思是我们检查在运行测试时是否在对象上执行了正确的方法和路径。 这与存根的支持作用完全不同,存根用于为您要测试的任何内容提供结果。 在存根中,我们使用为方法定义返回值的模式。
when(customer.getSurname()).thenReturn(surname);
在模拟中,我们使用以下形式检查对象的行为。
verify(listMock).add(s);
这是一个简单的示例,我们要测试新交易是否已正确审核。
这是主要代码。
public class SimpleTradingService implements TradingService{TradeRepository tradeRepository;AuditService auditService;public SimpleTradingService(TradeRepository tradeRepository, AuditService auditService){this.tradeRepository = tradeRepository;this.auditService = auditService;}public Long createTrade(Trade trade) throws CreateTradeException {Long id = tradeRepository.createTrade(trade);auditService.logNewTrade(trade);return id;
}
以下测试为交易存储库创建存根,并为AuditService创建模拟
然后,我们在模拟的AuditService上调用verify,以确保TradeService调用了
logNewTrade方法正确
@Mock
TradeRepository tradeRepository;@Mock
AuditService auditService;@Test
public void testAuditLogEntryMadeForNewTrade() throws Exception { Trade trade = new Trade('Ref 1', 'Description 1');when(tradeRepository.createTrade(trade)).thenReturn(anyLong()); TradingService tradingService = new SimpleTradingService(tradeRepository, auditService);tradingService.createTrade(trade);verify(auditService).logNewTrade(trade);
}
以下行对模拟的AuditService进行检查。
verify(auditService).logNewTrade(trade);
该测试使我们能够证明审计服务在创建交易时的行为正确。
测试间谍
http://xunitpatterns.com/Test%20Spy.html
值得一看上面的链接,以严格定义测试间谍。
但是,在Mockito中,我喜欢使用它来包装实际对象,然后验证或修改其行为以支持您的测试。 这是一个示例,我们检查了列表的标准行为。 注意,我们既可以验证是否调用了add方法,也可以断言该项目已添加到列表中。
@Spy
List listSpy = new ArrayList();@Test
public void testSpyReturnsRealValues() throws Exception {String s = 'dobie';listSpy.add(new String(s));verify(listSpy).add(s);assertEquals(1, listSpy.size());
}
将其与仅可验证方法调用的模拟对象进行比较。 因为我们仅模拟列表的行为,所以它不记录已添加项目,并且在调用size()方法时返回默认值零。
@Mock
ListlistMock = new ArrayList();@Test
public void testMockReturnsZero() throws Exception {String s = 'dobie';listMock.add(new String(s));verify(listMock).add(s);assertEquals(0, listMock.size());
}
testSpy的另一个有用功能是能够对返回调用进行存根。 完成此操作后,该对象将正常运行,直到调用存根方法为止。
在此示例中,我们将get方法存根以始终引发RuntimeException。 其余行为保持不变。
@Test(expected=RuntimeException.class)
public void testSpyReturnsStubbedValues() throws Exception {listSpy.add(new String('dobie')); assertEquals(1, listSpy.size());when(listSpy.get(anyInt())).thenThrow(new RuntimeException());listSpy.get(0);
}
在此示例中,我们再次保留了核心行为,但更改了size()方法,使其最初返回1,对所有后续调用返回5。
public void testSpyReturnsStubbedValues2() throws Exception {int size = 5;when(listSpy.size()).thenReturn(1, size);int mockedListSize = listSpy.size();assertEquals(1, mockedListSize);mockedListSize = listSpy.size();assertEquals(5, mockedListSize); mockedListSize = listSpy.size();assertEquals(5, mockedListSize);
}
这真是不可思议!
假物件
http://xunitpatterns.com/Fake%20Object.html
假物品通常是手工制作或重量较轻的物品,仅用于测试,不适合生产。 一个很好的例子是内存数据库或伪造的服务层。
它们往往提供比标准测试倍增功能更多的功能,因此通常不适合使用Mockito进行实现。 这并不是说它们不能像这样构造,仅仅是因为它可能不值得采用这种方式来实现。
参考:“ 小事半解 ” – 敏捷工程技术博客上来自我们JCG合作伙伴 John Dobie的Mockito了解测试双打 。
翻译自: https://www.javacodegeeks.com/2012/05/mocks-and-stubs-understanding-test.html