Programování

Mocks And Stubs - Understanding Test Doubles With Mockito

Běžnou věcí, na kterou narazím, je, že týmy využívající zesměšňující rámec předpokládají, že se vysmívají.

Nevědí, že Mocks jsou jen jedním z řady „testovacích čtyřhry“, které Gerard Meszaros kategorizoval na xunitpatterns.com.

Je důležité si uvědomit, že každý typ testovacího dvojníka má při testování jinou roli. Stejně jako se musíte naučit různé vzorce nebo refaktorování, musíte pochopit primitivní role každého typu testovacího dvojníka. Ty pak mohou být kombinovány, aby bylo dosaženo vašich testovacích potřeb.

Pokryji velmi stručnou historii toho, jak k této klasifikaci došlo a jak se každý z typů liší.

Udělám to na několika krátkých jednoduchých příkladech v Mockitu.

Lidé už léta píší odlehčené verze systémových komponent, aby jim pomohli s testováním. Obecně se tomu říkalo stubbing. V roce 2000 představil článek „Endo-Testing: Unit Testing with Mock Objects“ koncept Mock Object. Od té doby Meszaros klasifikoval Stubs, Mocks a řadu dalších typů testovacích předmětů jako Test Doubles.

Na tuto terminologii odkazoval Martin Fowler v dokumentu „Mocks Arn't Stubs“ a je přijímán v komunitě Microsoft, jak je uvedeno v „Exploring The Continuum of Test Doubles“

Odkaz na každý z těchto důležitých článků je uveden v referenční části.

Výše uvedený diagram ukazuje běžně používané typy testovacích dvojitých. Následující adresa URL poskytuje dobrý křížový odkaz na každý ze vzorů a jejich funkcí a také na alternativní terminologii.

//xunitpatterns.com/Test%20Double.html

Mockito je testovací špionážní rámec a je velmi snadné se ho naučit. Pozoruhodné u Mockita je, že očekávání jakýchkoli falešných objektů nejsou před testem definována, protože někdy jsou v jiných falešných rámcích. To vede k přirozenějšímu stylu (IMHO), když se začnete vysmívat.

Následující příklady jsou zde čistě proto, aby poskytly jednoduchou ukázku použití Mockita k implementaci různých typů testovacích dvojic.

Na webu existuje mnohem větší počet konkrétních příkladů, jak používat Mockito.

//docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html

Níže uvádíme několik základních příkladů, jak pomocí Mockita ukázat roli každého testovacího dvojníka definovaného Meszarosem.

Pro každou z nich jsem uvedl odkaz na hlavní definici, abyste mohli získat více příkladů a úplnou definici.

//xunitpatterns.com/Dummy%20Object.html

Toto je nejjednodušší ze všech testovacích dvoulůžkových pokojů. Toto je objekt, který nemá žádnou implementaci, která se používá čistě k naplnění argumentů volání metod, které jsou pro váš test irelevantní.

Například níže uvedený kód používá k vytvoření zákazníka hodně kódu, což pro test není důležité.

Test se nestaral o to, který zákazník je přidán, pokud se počet zákazníků vrátí jako jeden.

public Customer createDummyCustomer () {County county = new County ("Essex"); City city = new City ("Romford", county); Adresa adresa = nová adresa ("1234 Bank Street", město); Zákazník zákazník = nový zákazník ("john", "dobie", adresa); vrátit zákazníka; } @Test public void addCustomerTest () {Customer dummy = createDummyCustomer (); AddressBook addressBook = nový AddressBook (); addressBook.addCustomer (figurína); assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Ve skutečnosti se nestaráme o obsah objektu zákazníka - ale je to nutné. Můžeme zkusit nulovou hodnotu, ale pokud je kód správný, očekávali byste, že bude vyvolána nějaká výjimka.

@Test (očekává se = Exception.class) public void addNullCustomerTest () {Customer dummy = null; AddressBook addressBook = nový AddressBook (); addressBook.addCustomer (figurína); } 

Abychom tomu zabránili, můžeme k dosažení požadovaného chování použít jednoduchou figurínu Mockito.

@Test public void addCustomerWithDummyTest () {Customer dummy = mock (Customer.class); AddressBook addressBook = nový AddressBook (); addressBook.addCustomer (figurína); Assert.assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Je to tento jednoduchý kód, který vytváří fiktivní objekt, který má být předán do hovoru.

Figurína zákazníka = falešná (Customer.class);

Nenechte se zmást falešnou syntaxí - zde se hraje role figuríny, nikoli falešné.

Rozlišuje to role testovacího dvojníka, nikoli syntaxe použitá k jeho vytvoření.

Tato třída funguje jako jednoduchá náhrada za třídu zákazníků a umožňuje snadné čtení testu.

//xunitpatterns.com/Test%20Stub.html

Úlohou testovacího pahýlu je vrátit kontrolované hodnoty testovanému objektu. Ty jsou popsány jako nepřímé vstupy do testu. Doufejme, že příklad objasní, co to znamená.

Vezměte následující kód

veřejná třída SimplePricingService implementuje PricingService {úložiště PricingRepository; public SimplePricingService (PricingRepository pricingRepository) {this.repository = pricingRepository; } @Override public Price priceTrade (Trade trade) {return repository.getPriceForTrade (trade); } @Override public Price getTotalPriceForTrades (inkasní obchody) {Price totalPrice = new Price (); for (Trade trade: trades) {Price tradePrice = repository.getPriceForTrade (trade); totalPrice = totalPrice.add (tradePrice); } návratnost totalPrice; } 

SimplePricingService má jeden spolupracující objekt, kterým je úložiště obchodních údajů. Repozitář obchodních údajů poskytuje obchodní ceny cenové službě prostřednictvím metody getPriceForTrade.

Abychom mohli otestovat logiku businees v SimplePricingService, musíme tyto nepřímé vstupy ovládat

tj. vstupy, které jsme do testu nikdy neprošli.

Toto je uvedeno níže.

V následujícím příkladu zakryjeme PricingRepository, abychom vrátili známé hodnoty, které lze použít k testování obchodní logiky SimpleTradeService.

@Test public void testGetHighestPricedTrade () vyvolá výjimku {Cena cena1 = nová Cena (10); Cena cena2 = nová cena (15); Cena cena3 = nová cena (25); PricingRepository pricingRepository = falešný (PricingRepository.class); when (pricingRepository.getPriceForTrade (any (Trade.class))) .thenReturn (price1, price2, price3); Služba PricingService = nová SimplePricingService (pricingRepository); Cena nejvyšší cena = service.getHighestPricedTrade (getTrades ()); assertEquals (price3.getAmount (), nejvyššíPrice.getAmount ()); } 

Příklad Sabotéra

Existují 2 běžné varianty testovacích pahýlů: Responder's a Saboteur's.

Respondenti se používají k testování šťastné cesty jako v předchozím příkladu.

Sabotér se používá k testování výjimečného chování, jak je uvedeno níže.

@Test (očekává se = TradeNotFoundException.class) public void testInvalidTrade () vyvolá výjimku {Trade trade = new FixtureHelper (). GetTrade (); TradeRepository tradeRepository = falešný (TradeRepository.class); when (tradeRepository.getTradeById (anyLong ())) .thenThrow (new TradeNotFoundException ()); TradingService tradingService = nový SimpleTradingService (tradeRepository); tradingService.getTradeById (trade.getId ()); } 

//xunitpatterns.com/Mock%20Object.html

Mock objekty se používají k ověření chování objektu během testu. Chováním objektu myslím, že zkontrolujeme, zda jsou na objektu při spuštění testu prováděny správné metody a cesty.

To se velmi liší od podpůrné role útržku, který se používá k poskytování výsledků bez ohledu na to, co testujete.

V pahýlu použijeme vzor definování návratové hodnoty pro metodu.

when (customer.getSurname ()). thenReturn (surname); 

Falešně kontrolujeme chování objektu pomocí následujícího formuláře.

ověřit (listMock) .add; s; 

Zde je jednoduchý příklad, kdy chceme otestovat, zda je nový obchod auditován správně.

Zde je hlavní kód.

veřejná třída SimpleTradingService implementuje TradingService {TradeRepository tradeRepository; AuditService auditService; public SimpleTradingService (TradeRepository tradeRepository, AuditService auditService) {this.tradeRepository = tradeRepository; this.auditService = auditService; } public Long createTrade (Trade trade) hodí CreateTradeException {Long id = tradeRepository.createTrade (trade); auditService.logNewTrade (obchod); návratové ID; } 

Níže uvedený test vytvoří útržek pro úložiště obchodních údajů a falešný pro AuditService

Poté zavoláme ověření na posměšném AuditService, abychom se ujistili, že TradeService volá

metoda logNewTrade správně

@Mock TradeRepository tradeRepository; @Mock AuditService auditService; @Test public void testAuditLogEntryMadeForNewTrade () vyvolá výjimku {Trade trade = new Trade ("Ref 1", "Description 1"); kdy (tradeRepository.createTrade (obchod)). thenReturn (anyLong ()); TradingService tradingService = nový SimpleTradingService (tradeRepository, auditService); tradingService.createTrade (obchod); ověřit (auditService) .logNewTrade (obchod); } 

Následující řádek provádí kontrolu na posměšném AuditService.

ověřit (auditService) .logNewTrade (obchod);

Tento test nám umožňuje ukázat, že se auditorská služba chová při vytváření obchodu správně.

//xunitpatterns.com/Test%20Spy.html

Stojí za to podívat se na výše uvedený odkaz pro přísnou definici testovacího špiona.

V Mockito jej však rád používám, aby vám umožnil zabalit skutečný objekt a poté ověřit nebo upravit jeho chování, aby podpořilo vaše testování.

Zde je příklad, kdy jsme zkontrolovali standardní chování seznamu. Všimněte si, že můžeme ověřit, že je metoda přidání volána, a také tvrdit, že položka byla přidána do seznamu.

@Spy List listSpy = nový ArrayList (); @Test public void testSpyReturnsRealValues ​​() vyvolá výjimku {String s = "dobie"; listSpy.add (nové řetězce); ověřit (listSpy) .add (s); assertEquals (1, listSpy.size ()); } 

Porovnejte to s použitím falešného objektu, kde lze ověřit pouze volání metody. Protože se jen vysmíváme chování seznamu, nezaznamenává to, že byla položka přidána, a vrátí výchozí hodnotu nula, když voláme metodu size ().

@Mock List listMock = nový ArrayList (); @Test public void testMockReturnsZero () vyvolá výjimku {String s = "dobie"; listMock.add (nové řetězce); ověřit (listMock) .add (s); assertEquals (0, listMock.size ()); } 

Další užitečnou funkcí testSpy je schopnost potlačit zpětná volání. Když je to provedeno, objekt se bude chovat jako obvykle, dokud nebude vyvolána metoda stubbed.

V tomto příkladu použijeme metodu get, která vždy vyvolá RuntimeException. Zbytek chování zůstává stejný.

@Test (očekává se = RuntimeException.class) public void testSpyReturnsStubbedValues ​​() vyvolá výjimku {listSpy.add (nový řetězec ("dobie"))); assertEquals (1, listSpy.size ()); when (listSpy.get (anyInt ())). thenThrow (new RuntimeException ()); listSpy.get (0); } 

V tomto příkladu opět ponecháme základní chování, ale změníme metodu size () tak, aby vrátila 1 zpočátku a 5 pro všechna následující volání.

public void test SpyReturnsStubbedValues2 () vyvolá výjimku {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); } 

To je docela magické!

//xunitpatterns.com/Fake%20Object.html

Falešné předměty jsou obvykle ručně vyráběné nebo lehké předměty, které se používají pouze pro testování a nejsou vhodné pro výrobu. Dobrým příkladem by byla databáze v paměti nebo falešná servisní vrstva.

Mají tendenci poskytovat mnohem více funkcí než standardní testovací zdvojnásobení a jako takové pravděpodobně nejsou obvykle kandidáty na implementaci pomocí Mockita. To neznamená, že by nemohly být konstruovány jako takové, jen to pravděpodobně nestojí za implementaci tímto způsobem.

Vyzkoušejte dvojité vzory

Endo-Testing: Unit Uniting with Mock Objects

Falešné role, ne objekty

Mocks nejsou pahýly

//msdn.microsoft.com/en-us/magazine/cc163358.aspx

Tento příběh „Mocks And Stubs - Understanding Test Doubles With Mockito“ byl původně publikován společností JavaWorld.