Programmering

Mocks And Stubs - Forståelse af testdobler med Mockito

En almindelig ting, jeg støder på, er at hold, der bruger en spottende ramme, antager, at de spotter.

De er ikke opmærksomme på, at Mocks kun er en af ​​et antal 'Test Doubles', som Gerard Meszaros har kategoriseret på xunitpatterns.com.

Det er vigtigt at indse, at hver type testdobbelt har en anden rolle at spille i testningen. På samme måde som du har brug for at lære forskellige mønstre eller refactoring, skal du forstå de primitive roller for hver type test dobbelt. Disse kan derefter kombineres for at nå dine testbehov.

Jeg vil dække en meget kort historie om, hvordan denne klassificering opstod, og hvordan hver af typerne adskiller sig.

Jeg gør dette ved hjælp af nogle korte, enkle eksempler i Mockito.

I årevis har folk skrevet lette versioner af systemkomponenter for at hjælpe med testning. Generelt blev det kaldt stubbing. I 2000 introducerede artiklen 'Endo-Testing: Unit Testing with Mock Objects' konceptet med et Mock Object. Siden da er stubs, Mocks og en række andre typer testgenstande klassificeret af Meszaros som Test Doubles.

Denne terminologi er blevet henvist til af Martin Fowler i "Mocks Aren't Stubs" og er ved at blive vedtaget inden for Microsoft-samfundet som vist i "Exploring The Continuum of Test Doubles"

Et link til hvert af disse vigtige papirer vises i referenceafsnittet.

Ovenstående diagram viser de almindeligt anvendte typer af dobbelt test. Den følgende URL giver en god krydshenvisning til hvert af mønstrene og deres funktioner samt alternativ terminologi.

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

Mockito er en testspionramme, og det er meget simpelt at lære. Bemærkelsesværdigt med Mockito er, at forventninger til eventuelle mock-objekter ikke defineres før testen, da de undertiden er i andre mocking-rammer. Dette fører til en mere naturlig stil (IMHO), når man begynder at spotte.

De følgende eksempler er her udelukkende for at give en enkel demonstration af, hvordan man bruger Mockito til at implementere de forskellige typer testdobler.

Der er et meget større antal specifikke eksempler på, hvordan man bruger Mockito på hjemmesiden.

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

Nedenfor er nogle grundlæggende eksempler, der bruger Mockito til at vise rollen for hver test dobbelt som defineret af Meszaros.

Jeg har inkluderet et link til hoveddefinitionen for hver, så du kan få flere eksempler og en komplet definition.

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

Dette er den enkleste af alle testdoblerne. Dette er et objekt, der ikke har nogen implementering, der udelukkende bruges til at udfylde argumenter for metodekald, der er irrelevante for din test.

For eksempel bruger koden nedenfor en masse kode til at oprette kunden, hvilket ikke er vigtigt for testen.

Testen kunne ikke være ligeglad med, hvilken kunde der blev tilføjet, så længe kundetællingen kommer tilbage som en.

offentlig kunde createDummyCustomer () {County county = new County ("Essex"); Byby = ny by ("Romford", amt); Adresse-adresse = ny adresse ("1234 Bank Street", by); Kundekunde = ny kunde ("john", "dobie", adresse); tilbagevendende kunde; } @ Test offentligt ugyldigt addCustomerTest () {Kundedummy = createDummyCustomer (); Adressebog adressebog = ny Adressebog (); addressBook.addCustomer (dummy); assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Vi er ligeglad med indholdet af kundeobjektet - men det er påkrævet. Vi kan prøve en nulværdi, men hvis koden er korrekt, forventer du, at der smides en slags undtagelse.

@Test (forventet = Exception.class) offentlig ugyldig addNullCustomerTest () {Customer dummy = null; Adressebog adressebog = ny adressebog (); addressBook.addCustomer (dummy); } 

For at undgå dette kan vi bruge en simpel Mockito dummy for at få den ønskede adfærd.

@Test offentligt ugyldigt addCustomerWithDummyTest () {Customer dummy = mock (Customer.class); Adressebog adressebog = ny adressebog (); addressBook.addCustomer (dummy); Assert.assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Det er denne enkle kode, der skaber et dummy-objekt, der skal sendes til opkaldet.

Kundedummy = mock (Customer.class);

Lad dig ikke narre af den mock-syntaks - den rolle, der spilles her, er en dummy, ikke en mock.

Det er rollen som testdobbelt, der adskiller den, ikke den syntaks, der bruges til at oprette en.

Denne klasse fungerer som en simpel erstatning for kundeklassen og gør testen meget let at læse.

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

Teststubens rolle er at returnere kontrollerede værdier til det objekt, der testes. Disse beskrives som indirekte input til testen. Forhåbentlig kan et eksempel afklare, hvad dette betyder.

Tag følgende kode

offentlig klasse SimplePricingService implementerer PricingService {PricingRepository repository; offentlig SimplePricingService (PricingRepository pricingRepository) {this.repository = pricingRepository; } @ Override public Price priceTrade (Trade trade) {return repository.getPriceForTrade (trade); } @ Override public Price getTotalPriceForTrades (Collection trades) {Price totalPrice = new Price (); for (Trade trade: trades) {Price tradePrice = repository.getPriceForTrade (trade); totalPrice = totalPrice.add (tradePrice); } returner totalPris; } 

SimplePricingService har et samarbejdsobjekt, som er transaktionsregistret. Transaktionsregisteret giver handelspriser til prissætningen via getPriceForTrade-metoden.

For at vi kan teste forretningslogikken i SimplePricingService, er vi nødt til at kontrollere disse indirekte input

dvs. input, vi aldrig har bestået i testen.

Dette er vist nedenfor.

I det følgende eksempel stubber vi PricingRepository for at returnere kendte værdier, som kan bruges til at teste forretningslogikken for SimpleTradeService.

@Test offentlig ugyldig testGetHighestPricedTrade () kaster undtagelse {Prispris1 = ny pris (10); Pris pris2 = ny pris (15); Pris pris3 = ny pris (25); PricingRepository pricingRepository = mock (PricingRepository.class); når (prissætningRepository.getPriceForTrade (enhver (Trade.class)) .thenReturn (pris1, pris2, pris3); PricingService service = ny SimplePricingService (pricingRepository); Pris højstPris = service.getHighestPricedTrade (getTrades ()); assertEquals (pris3.getAmount (), højeste pris.getAmount ()); } 

Sabotøreksempel

Der er 2 almindelige varianter af teststubber: Responder's og Saboteur's.

Responder's bruges til at teste den lykkelige vej som i det foregående eksempel.

En sabotør bruges til at teste usædvanlig adfærd som nedenfor.

@Test (forventet = TradeNotFoundException.class) public void testInvalidTrade () kaster Exception {Trade trade = new FixtureHelper (). GetTrade (); TradeRepository tradeRepository = mock (TradeRepository.class); når (tradeRepository.getTradeById (anyLong ())) .thenThrow (nyt TradeNotFoundException ()); TradingService tradingService = ny SimpleTradingService (tradeRepository); tradingService.getTradeById (trade.getId ()); } 

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

Mock objekter bruges til at verificere objektadfærd under en test. Ved objektadfærd mener jeg, at vi kontrollerer, at de korrekte metoder og stier udøves på objektet, når testen køres.

Dette adskiller sig meget fra en stubs understøttende rolle, som bruges til at give resultater til det, du tester.

I en stub bruger vi mønsteret til at definere en returværdi for en metode.

når (customer.getSname ()). derefterReturn (efternavn); 

I en mock kontrollerer vi objektets opførsel ved hjælp af følgende form.

verificere (listMock) .add (s); 

Her er et simpelt eksempel, hvor vi vil teste, at en ny handel revideres korrekt.

Her er hovedkoden.

offentlig klasse SimpleTradingService implementerer TradingService {TradeRepository tradeRepository; AuditService auditService; offentlig SimpleTradingService (TradeRepository tradeRepository, AuditService auditService) {this.tradeRepository = tradeRepository; this.auditService = auditService; } offentlig lang createTrade (handel handel) kaster CreateTradeException {Long id = tradeRepository.createTrade (handel); auditService.logNewTrade (handel); retur-id; } 

Testen nedenfor skaber en stub til transaktionsregistret og mock for AuditService

Vi kalder derefter bekræftelse på den hånede AuditService for at sikre, at TradeService kalder det

logNewTrade-metoden korrekt

@Mock TradeRepository tradeRepository; @Mock AuditService auditService; @Test offentlig ugyldig testAuditLogEntryMadeForNewTrade () kaster undtagelse {Trade trade = new Trade ("Ref 1", "Description 1"); når (tradeRepository.createTrade (handel)). derefterReturn (anyLong ()); TradingService tradingService = ny SimpleTradingService (tradeRepository, auditService); tradingService.createTrade (handel); verificere (auditService) .logNewTrade (handel); } 

Den følgende linje kontrollerer den mocked AuditService.

verificere (auditService) .logNewTrade (handel);

Denne test giver os mulighed for at vise, at revisionstjenesten opfører sig korrekt, når vi opretter en handel.

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

Det er værd at kigge på ovenstående link for en streng definition af en testspion.

Men i Mockito kan jeg godt lide at bruge det til at give dig mulighed for at pakke et rigtigt objekt og derefter kontrollere eller ændre dets adfærd for at understøtte din test.

Her er et eksempel, hvor vi kontrollerede en listes standardopførsel. Bemærk, at vi både kan kontrollere, at tilføjelsesmetoden kaldes, og også hævde, at varen blev føjet til listen.

@Spy List listSpy = ny ArrayList (); @Test offentlig ugyldig testSpyReturnsRealValues ​​() kaster undtagelse {String s = "dobie"; listSpy.add (nye streng (er)); verificere (listSpy) .add (s); assertEquals (1, listSpy.size ()); } 

Sammenlign dette med at bruge et mock-objekt, hvor kun metodekaldet kan valideres. Da vi kun håner listenes opførsel, registrerer den ikke, at varen er tilføjet, og returnerer standardværdien nul, når vi kalder størrelsesmetoden ().

@Mock List listMock = ny ArrayList (); @Test offentlig ugyldig testMockReturnsZero () kaster undtagelse {String s = "dobie"; listMock.add (nye streng (er)); verificere (listMock) .add (s); assertEquals (0, listMock.size ()); } 

En anden nyttig funktion i testSpy er evnen til at stoppe returopkald. Når dette er gjort, vil objektet opføre sig som normalt, indtil den stubede metode kaldes.

I dette eksempel stubber vi get-metoden til altid at kaste en RuntimeException. Resten af ​​adfærden forbliver den samme.

@Test (forventet = RuntimeException.class) offentlig ugyldighed testSpyReturnsStubbedValues ​​() kaster undtagelse {listSpy.add (ny streng ("dobie")); assertEquals (1, listSpy.size ()); når (listSpy.get (anyInt ())). derefter Kast (ny RuntimeException ()); listSpy.get (0); } 

I dette eksempel beholder vi igen kerneadfærden, men ændrer størrelsesmetoden () for først at returnere 1 og 5 for alle efterfølgende opkald.

public void testSpyReturnsStubbedValues2 () kaster Undtagelse {int størrelse = 5; når (listSpy.size ()). derefter Retur (1, størrelse); int mockedListSize = listSpy.size (); assertEquals (1, mockedListSize); mockedListSize = listSpy.size (); assertEquals (5, mockedListSize); mockedListSize = listSpy.size (); assertEquals (5, mockedListSize); } 

Dette er ret magisk!

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

Falske genstande er normalt håndlavede eller lette genstande, der kun bruges til test og ikke egner sig til produktion. Et godt eksempel ville være en database i hukommelsen eller et falskt servicelag.

De har tendens til at give meget mere funktionalitet end standard testdobler og er som sådan sandsynligvis normalt ikke kandidater til implementering ved hjælp af Mockito. Det betyder ikke, at de ikke kunne konstrueres som sådan, bare at det sandsynligvis ikke er værd at implementere på denne måde.

Test dobbelt mønstre

Endo-test: Enhedstest med mock objekter

Mock-roller, ikke genstande

Mocks er ikke stubbe

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

Denne historie, "Mocks And Stubs - Understanding Test Doubles With Mockito" blev oprindeligt udgivet af JavaWorld.