Programmering

Mocks And Stubs - Forstå testdobler med Mockito

En vanlig ting jeg kommer over er at lag som bruker et hånlig rammeverk, antar at de håner.

De er ikke klar over at Mocks bare er en av en rekke 'Test Doubles' som Gerard Meszaros har kategorisert på xunitpatterns.com.

Det er viktig å innse at hver type testdobbel har en annen rolle å spille i testing. På samme måte som du trenger å lære forskjellige mønstre eller refactoring, må du forstå de primitive rollene til hver type testdobbel. Disse kan deretter kombineres for å oppnå dine testbehov.

Jeg vil dekke en veldig kort historie om hvordan denne klassifiseringen ble til, og hvordan hver av typene er forskjellige.

Jeg gjør dette ved hjelp av noen korte, enkle eksempler i Mockito.

I mange år har folk skrevet lette versjoner av systemkomponenter for å hjelpe til med testing. Generelt ble det kalt stubbing. I 2000 introduserte artikkelen 'Endo-Testing: Unit Testing with Mock Objects' konseptet med et Mock Object. Siden den gang har Stubs, Mocks og en rekke andre typer testobjekter blitt klassifisert av Meszaros som Test Doubles.

Denne terminologien er referert av Martin Fowler i "Mocks Aren't Stubs" og blir vedtatt i Microsoft-fellesskapet som vist i "Exploring The Continuum of Test Doubles"

En lenke til hvert av disse viktige papirene er vist i referansedelen.

Diagrammet over viser de vanlige testtypene. Den følgende URL-en gir en god kryssreferanse til hvert av mønstrene og deres funksjoner, samt alternativ terminologi.

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

Mockito er et testspioneringsrammeverk, og det er veldig enkelt å lære. Bemerkelsesverdig med Mockito er at forventningene til eventuelle mock-objekter ikke er definert før testen, slik de noen ganger er i andre mocking-rammer. Dette fører til en mer naturlig stil (IMHO) når man begynner å spotte.

Følgende eksempler er her bare for å gi en enkel demonstrasjon av å bruke Mockito til å implementere de forskjellige typene testdobler.

Det er et mye større antall spesifikke eksempler på hvordan du bruker Mockito på nettstedet.

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

Nedenfor er noen grunnleggende eksempler som bruker Mockito for å vise rollen til hver test dobbelt som definert av Meszaros.

Jeg har tatt med en lenke til hoveddefinisjonen for hver, slik at du kan få flere eksempler og en fullstendig definisjon.

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

Dette er den enkleste av alle testdoblene. Dette er et objekt som ikke har noen implementering som bare brukes til å fylle ut argumenter for metodesamtaler som er irrelevante for testen din.

For eksempel bruker koden nedenfor mye kode for å opprette kunden som ikke er viktig for testen.

Testen bryr seg ikke mindre om hvilken kunde som er lagt til, så lenge kundetellingen kommer tilbake som en.

offentlig kunde createDummyCustomer () {County county = new County ("Essex"); Byby = ny by ("Romford", fylke); Adresse-adresse = ny adresse ("1234 Bank Street", by); Kundekunde = ny kunde ("john", "dobie", adresse); retur kunde; } @Test offentlig ugyldig addCustomerTest () {Customer dummy = createDummyCustomer (); Adressebok adressebok = ny adressebok (); addressBook.addCustomer (dummy); assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Vi bryr oss faktisk ikke om innholdet i kundeobjektet - men det er nødvendig. Vi kan prøve en nullverdi, men hvis koden er riktig, forventer du at det blir kastet noe unntak.

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

For å unngå dette kan vi bruke en enkel Mockito-dummy for å oppnå ønsket oppførsel.

@Test offentlig ugyldig addCustomerWithDummyTest () {Customer dummy = mock (Customer.class); Adressebok adressebok = ny adressebok (); addressBook.addCustomer (dummy); Assert.assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Det er denne enkle koden som skaper et dummyobjekt som skal sendes inn i samtalen.

Kundedummy = mock (Customer.class);

Ikke la deg lure av mock-syntaksen - rollen som spilles her er den av en dummy, ikke en mock.

Det er rollen til testdobbeltet som skiller den fra hverandre, ikke syntaksen som brukes til å lage en.

Denne klassen fungerer som en enkel erstatning for kundeklassen og gjør testen veldig lett å lese.

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

Teststubbens rolle er å returnere kontrollerte verdier til objektet som testes. Disse er beskrevet som indirekte innganger til testen. Forhåpentligvis vil et eksempel avklare hva dette betyr.

Ta følgende kode

offentlig klasse SimplePricingService implementerer PricingService {PricingRepository repository; offentlig SimplePricingService (PricingRepository pricingRepository) {this.repository = pricingRepository; } @ Override public Price priceTrade (Trade trade) {retur 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 ett samarbeidsobjekt som er handelsregisteret. Handelsregisteret gir handelspriser til pristjenesten gjennom getPriceForTrade-metoden.

For at vi skal kunne teste forretningslogikken i SimplePricingService, må vi kontrollere disse indirekte inngangene

dvs. innganger vi aldri gikk inn i testen.

Dette er vist nedenfor.

I det følgende eksemplet stikker vi PricingRepository for å returnere kjente verdier som kan brukes til å teste forretningslogikken til SimpleTradeService.

@Test offentlig ugyldig testGetHighestPricedTrade () kaster unntak {Prispris1 = ny pris (10); Pris pris2 = ny pris (15); Pris pris3 = ny pris (25); PricingRepository pricingRepository = mock (PricingRepository.class); når (prisingRepository.getPriceForTrade (hvilken som helst (Trade.class)) .thenReturn (pris1, pris2, pris3); PricingService-tjeneste = ny SimplePricingService (pricingRepository); Pris høyestPris = service.getHighestPricedTrade (getTrades ()); assertEquals (price3.getAmount (), høyeste pris.getAmount ()); } 

Sabotøreksempel

Det er to vanlige varianter av teststubber: Responder’s og Saboteur's.

Responder's brukes til å teste den lykkelige banen som i forrige eksempel.

En sabotør brukes til å teste eksepsjonell oppførsel som nedenfor.

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

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

Mock objekter brukes til å verifisere objektets oppførsel under en test. Med objektadferd mener jeg at vi sjekker at riktige metoder og baner utøves på objektet når testen kjøres.

Dette er veldig annerledes enn støtterollen til en stub som brukes til å gi resultater til det du tester.

I en stub bruker vi mønsteret for å definere en returverdi for en metode.

når (customer.getSname ()). deretterReturn (etternavn); 

I en hån kontrollerer vi oppførselen til objektet ved hjelp av følgende skjema.

verifisere (listMock) .add (s); 

Her er et enkelt eksempel der vi vil teste at en ny handel blir revidert riktig.

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 Long createTrade (Trade trade) kaster CreateTradeException {Long id = tradeRepository.createTrade (trade); auditService.logNewTrade (handel); retur id; } 

Testen nedenfor skaper en stubbe for transaktionsregisteret og spott for AuditService

Vi kaller deretter verifisere på den hånede AuditService for å sikre at TradeService kaller det

logNewTrade-metoden riktig

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

Følgende linje kontrollerer på den hånede AuditService.

verifisere (auditService) .logNewTrade (handel);

Denne testen lar oss vise at revisjonstjenesten oppfører seg riktig når vi oppretter en handel.

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

Det er verdt å se på lenken ovenfor for å få en streng definisjon av en testspion.

Imidlertid liker jeg å bruke den i Mockito slik at du kan pakke inn et ekte objekt og deretter bekrefte eller endre atferden for å støtte testen.

Her er et eksempel der vi sjekket standardoppførselen til en liste. Merk at vi både kan bekrefte at add-metoden kalles og også hevde at varen ble lagt til listen.

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

Sammenlign dette med å bruke et mock-objekt der bare metodeanropet kan valideres. Fordi vi bare håner oppførselen til listen, registrerer den ikke at varen er lagt til og returnerer standardverdien null når vi kaller størrelsesmetoden ().

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

En annen nyttig funksjon i testSpy er muligheten til å stoppe returanrop. Når dette er gjort vil objektet oppføre seg som normalt til stubmetoden kalles.

I dette eksemplet stubber vi get-metoden for alltid å kaste et RuntimeException. Resten av oppførselen forblir den samme.

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

I dette eksemplet beholder vi igjen kjerneatferd, men endrer størrelsesmetoden () for å returnere 1 først og 5 for alle påfølgende samtaler.

public void testSpyReturnsStubbedValues2 () kaster Unntak {int size = 5; når (listSpy.size ()). deretter 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 ganske magisk!

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

Falske gjenstander er vanligvis håndlagde eller lette gjenstander som bare brukes til testing og ikke er egnet for produksjon. Et godt eksempel ville være en database i minnet eller et falskt tjenestelag.

De pleier å gi mye mer funksjonalitet enn standard testdobler, og som sådan er de vanligvis ikke kandidater for implementering ved bruk av Mockito. Det er ikke å si at de ikke kunne konstrueres som sådan, bare at det sannsynligvis ikke er verdt å implementere på denne måten.

Test doble mønstre

Endo-testing: Enhetstesting med mock objekter

Mock Rolles, Not Objects

Mocks er ikke stubber

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

Denne historien, "Mocks And Stubs - Understanding Test Doubles With Mockito" ble opprinnelig utgitt av JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found