嘲弄和存根–了解Mockito的测试双打
我遇到的一件事是使用模擬框架的團(tuán)隊(duì)假設(shè)他們?cè)谀M。
他們并不知道Mocks只是Gerard Meszaros在xunitpatterns.com上歸類的“測(cè)試雙打”之一。
重要的是要意識(shí)到每種類型的測(cè)試雙精度在測(cè)試中都扮演著不同的角色。 用與您需要學(xué)習(xí)不同模式或重構(gòu)的方式相同,您需要了解每種類型的測(cè)試double的原始角色。 然后可以將它們組合起來以滿足您的測(cè)試需求。
我將簡要介紹這種分類的產(chǎn)生方式以及每種類型的不同之處。
我將在Mockito中使用一些簡短的簡單示例進(jìn)行此操作。
非常簡短的歷史
多年來,人們一直在編寫系統(tǒng)組件的輕量級(jí)版本以幫助進(jìn)行測(cè)試。 通常將其稱為存根。 在2000年的文章“ Endo-Testing:使用模擬對(duì)象進(jìn)行單元測(cè)試”中介紹了模擬對(duì)象的概念。 從那時(shí)起,Meszaros將存根,模擬和其他許多類型的測(cè)試對(duì)象歸類為“測(cè)試雙打”。
該術(shù)語已由Martin Fowler在“ Mocks Are n't Stubs”中引用,并在Microsoft社區(qū)中被采用,如“ Exploring The Test Doubles Continuum of Test Doubles”中所示。
參考部分中顯示了指向這些重要論文的鏈接。
考試雙打的類別
上圖顯示了常用的雙重測(cè)試類型。 以下URL提供了對(duì)每個(gè)模式及其功能以及替代術(shù)語的很好的交叉引用。
http://xunitpatterns.com/Test%20Double.html
莫基托
Mockito是一個(gè)測(cè)試間諜框架,學(xué)習(xí)起來非常簡單。 Mockito值得注意的是,在測(cè)試之前沒有定義任何模擬對(duì)象的期望,因?yàn)樗鼈冇袝r(shí)在其他模擬框架中也是如此。 開始嘲笑時(shí),這會(huì)導(dǎo)致更自然的樣式(IMHO)。
以下示例在此處純粹是為了簡單演示如何使用Mockito實(shí)施不同類型的測(cè)試雙打。
網(wǎng)站上有大量有關(guān)如何使用Mockito的特定示例。
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html
使用Mockito測(cè)試雙打
以下是一些使用Mockito的基本示例,以顯示Meszaros定義的每個(gè)測(cè)試雙打的作用。
我為每個(gè)對(duì)象都提供了指向主要定義的鏈接,因此您可以獲得更多示例和完整定義。
虛擬對(duì)象
http://xunitpatterns.com/Dummy%20Object.html
這是所有測(cè)試雙打中最簡單的一次。 這是一個(gè)沒有實(shí)現(xiàn)的對(duì)象,僅用于填充與測(cè)試無關(guān)的方法調(diào)用的參數(shù)。
例如,下面的代碼使用很多代碼來創(chuàng)建客戶,這對(duì)測(cè)試并不重要。
只要客戶數(shù)恢復(fù)為1,該測(cè)試就不會(huì)在乎添加哪個(gè)客戶。
我們實(shí)際上并不關(guān)心客戶對(duì)象的內(nèi)容,但是它是必需的。 我們可以嘗試使用null值,但是如果代碼正確,則可能會(huì)引發(fā)某種異常。
@Test(expected=Exception.class) public void addNullCustomerTest() {Customer dummy = null;AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy); }為了避免這種情況,我們可以使用一個(gè)簡單的Mockito假人來獲得所需的行為。
@Test public void addCustomerWithDummyTest() {Customer dummy = mock(Customer.class);AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);Assert.assertEquals(1, addressBook.getNumberOfCustomers()); }正是這個(gè)簡單的代碼創(chuàng)建了一個(gè)要傳遞給調(diào)用的虛擬對(duì)象。
Customer dummy = mock(Customer.class); 不要被模擬語法所迷惑-這里扮演的角色是虛擬的,而不是模擬的。
區(qū)別在于測(cè)試雙重性的作用,而不是用于創(chuàng)建雙重性的語法。
該類可以輕松替代客戶類,并使測(cè)試非常容易閱讀。
測(cè)試存根
http://xunitpatterns.com/Test%20Stub.html
測(cè)試存根的作用是將受控值返回到要測(cè)試的對(duì)象。 這些被描述為測(cè)試的間接輸入。 希望有一個(gè)例子可以闡明這意味著什么。
采取以下代碼
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具有一個(gè)協(xié)作對(duì)象,即交易存儲(chǔ)庫。 交易存儲(chǔ)庫通過getPriceForTrade方法將交易價(jià)格提供給定價(jià)服務(wù)。
為了測(cè)試SimplePricingService中的業(yè)務(wù)邏輯,我們需要控制這些間接輸入
即我們從未通過測(cè)試的輸入。 如下所示。
在以下示例中,我們對(duì)PricingRepository存根以返回可用于測(cè)試SimpleTradeService的業(yè)務(wù)邏輯的已知值。
@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()); }破壞者的例子
測(cè)試存根有兩種常見的變體:響應(yīng)者和破壞者。
如前面的示例,使用響應(yīng)者來測(cè)試幸福的道路。
破壞者用于測(cè)試以下異常行為。
模擬對(duì)象
http://xunitpatterns.com/Mock%20Object.html
模擬對(duì)象用于在測(cè)試期間驗(yàn)證對(duì)象行為。 通過對(duì)象行為,我的意思是我們檢查在運(yùn)行測(cè)試時(shí)是否在對(duì)象上執(zhí)行了正確的方法和路徑。 這與存根的支持作用完全不同,存根用于為您要測(cè)試的任何內(nèi)容提供結(jié)果。 在存根中,我們使用為方法定義返回值的模式。
在模擬中,我們使用以下形式檢查對(duì)象的行為。
verify(listMock).add(s); 這是一個(gè)簡單的示例,我們要測(cè)試新交易是否已正確審核。
這是主要代碼。
以下測(cè)試為交易存儲(chǔ)庫創(chuàng)建存根,并為AuditService創(chuàng)建模擬
然后,我們?cè)谀M的AuditService上調(diào)用verify,以確保TradeService調(diào)用了
logNewTrade方法正確
以下行對(duì)模擬的AuditService進(jìn)行檢查。
verify(auditService).logNewTrade(trade);該測(cè)試使我們能夠證明審計(jì)服務(wù)在創(chuàng)建交易時(shí)的行為正確。
測(cè)試間諜
http://xunitpatterns.com/Test%20Spy.html
值得一看上面的鏈接,以嚴(yán)格定義測(cè)試間諜。
但是,在Mockito中,我喜歡使用它來包裝實(shí)際對(duì)象,然后驗(yàn)證或修改其行為以支持您的測(cè)試。 這是一個(gè)示例,我們檢查了列表的標(biāo)準(zhǔn)行為。 注意,我們既可以驗(yàn)證是否調(diào)用了add方法,也可以斷言該項(xiàng)目已添加到列表中。
將其與僅可驗(yàn)證方法調(diào)用的模擬對(duì)象進(jìn)行比較。 因?yàn)槲覀儍H模擬列表的行為,所以它不記錄已添加項(xiàng)目,并且在調(diào)用size()方法時(shí)返回默認(rèn)值零。
@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的另一個(gè)有用功能是能夠?qū)Ψ祷卣{(diào)用進(jìn)行存根。 完成此操作后,該對(duì)象將正常運(yùn)行,直到調(diào)用存根方法為止。
在此示例中,我們將get方法存根以始終引發(fā)RuntimeException。 其余行為保持不變。
在此示例中,我們?cè)俅伪A袅撕诵男袨?#xff0c;但更改了size()方法,使其最初返回1,對(duì)所有后續(xù)調(diào)用返回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
假物品通常是手工制作或重量較輕的物品,僅用于測(cè)試,不適合生產(chǎn)。 一個(gè)很好的例子是內(nèi)存數(shù)據(jù)庫或偽造的服務(wù)層。
它們往往提供比標(biāo)準(zhǔn)測(cè)試倍增功能更多的功能,因此通常不適合使用Mockito進(jìn)行實(shí)現(xiàn)。 這并不是說它們不能像這樣構(gòu)造,僅僅是因?yàn)樗赡懿恢档貌捎眠@種方式來實(shí)現(xiàn)。
參考:“ 小事半解 ” – 敏捷工程技術(shù)博客上來自我們JCG合作伙伴 John Dobie的Mockito了解測(cè)試雙打 。
翻譯自: https://www.javacodegeeks.com/2012/05/mocks-and-stubs-understanding-test.html
總結(jié)
以上是生活随笔為你收集整理的嘲弄和存根–了解Mockito的测试双打的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linuxc程序设计(linux c
- 下一篇: 使用Log4jdbc记录JDBC操作