Izrugivanja i šaranja - Razumijevanje probnih parova s ​​Mockitom

Uobičajena stvar s kojom se susrećem je da timovi koji koriste podsmiješni okvir pretpostavljaju da se rugaju.

Nisu svjesni da su rugalice samo jedan od niza 'Test parova' koje je Gerard Meszaros kategorizirao na xunitpatterns.com.

Važno je shvatiti da svaka vrsta testnog dvojnika ima različitu ulogu u testiranju. Na isti način na koji trebate naučiti različite uzorke ili refaktoriranje, morate razumjeti primitivne uloge svake vrste testnog dvojnika. Tada se mogu kombinirati kako bi se postigle vaše potrebe za testiranjem.

Objasnit ću vrlo kratku povijest kako je došlo do ove klasifikacije i kako se svaka od vrsta razlikuje.

Učinit ću to koristeći neke kratke, jednostavne primjere u Mockitu.

Godinama ljudi pišu lagane verzije komponenata sustava kako bi pomogli u testiranju. Općenito se to zvalo trljanje. 2000. 'članak' Endo-testiranje: jedinstveno testiranje s izmišljenim objektima 'predstavio je koncept lažnog objekta. Od tada je Stubs, Mocks i brojne druge vrste testnih predmeta Meszaros klasificirao kao testne parove.

Martin Fowler na ovu se terminologiju pozvao u "Mocks Aren't Stubs" i na nju se usvaja unutar Microsoftove zajednice, kao što je prikazano u "Istraživanje kontinuuma testnih dvojnika"

Poveznica do svakog od ovih važnih radova prikazana je u referentnom odjeljku.

Gornji dijagram prikazuje najčešće korištene vrste testnih dvostrukih. Sljedeći URL daje dobru unakrsnu referencu na svaki obrazac i njihove značajke, kao i na alternativnu terminologiju.

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

Mockito je testni špijunski okvir i vrlo je jednostavan za naučiti. Značajno kod Mockita je da očekivanja od bilo kakvih lažnih predmeta nisu definirana prije testa, jer su ponekad u drugim podsmiješnim okvirima. To dovodi do prirodnijeg stila (IMHO) na početku ruganja.

Sljedeći su primjeri ovdje čisto da bi pružili jednostavnu demonstraciju korištenja Mockita za provedbu različitih vrsta testnih parova.

Mnogo je veći broj konkretnih primjera kako se Mockito koristi na web mjestu.

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

Ispod su neki osnovni primjeri korištenja Mockita za prikaz uloge svakog testnog dvojnika kako ga je definirao Meszaros.

Uključio sam vezu do glavne definicije za svaku tako da možete dobiti više primjera i cjelovitu definiciju.

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

Ovo je najjednostavniji od svih testnih parova. Ovo je objekt koji nema implementaciju koji se koristi isključivo za popunjavanje argumenata poziva metode koji su irelevantni za vaš test.

Na primjer, kod u nastavku koristi puno koda za stvaranje kupca, što nije važno za test.

Testu nije bilo svejedno koji će se kupac dodati, sve dok se broj kupaca vrati kao jedan.

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()); } 

Zapravo nas nije briga za sadržaj korisničkog objekta - ali on je potreban. Možemo isprobati null vrijednost, ali ako je kôd točan, očekivali biste da će se pojaviti neka vrsta iznimke.

@Test(expected=Exception.class) public void addNullCustomerTest() { Customer dummy = null; AddressBook addressBook = new AddressBook(); addressBook.addCustomer(dummy); } 

Da bismo to izbjegli, možemo koristiti jednostavnu lutku Mockito da bismo postigli željeno ponašanje.

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

Ovaj jednostavni kôd stvara lažni objekt koji se prosljeđuje u poziv.

Customer dummy = mock(Customer.class);

Neka vas ne zavara lažna sintaksa - ovdje se igra uloga lutke, a ne lažnog.

Razlikuje ga uloga testnog dvojnika, a ne sintaksa korištena za njegovo stvaranje.

Ova klasa djeluje kao jednostavna zamjena za klasu kupaca i čini test vrlo lakim za čitanje.

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

Uloga testnog kvara je vratiti kontrolirane vrijednosti u objekt koji se ispituje. Oni su opisani kao neizravni ulazi u test. Nadam se da će primjer pojasniti što to znači.

Uzmite sljedeći kod

public class SimplePricingService implements PricingService { PricingRepository repository; public 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); } return totalPrice; } 

SimplePricingService ima jedan suradnički objekt, a to je spremište trgovine. Repozitorij trgovina pruža cijenu usluzi cijena putem usluge getPriceForTrade.

Da bismo testirali poslovnu logiku u programu SimplePricingService, moramo kontrolirati ove neizravne ulaze

tj. ulazi koje nikada nismo prošli u test.

To je prikazano u nastavku.

U sljedećem primjeru zaustavljamo PricingRepository kako bi vratili poznate vrijednosti koje se mogu koristiti za testiranje poslovne logike 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()); } 

Primjer sabotera

Postoje dvije uobičajene varijante ispitnih stubova: Responder's i Saboteur's.

Odgovarači se koriste za testiranje sretnog puta kao u prethodnom primjeru.

Saboter se koristi za testiranje iznimnog ponašanja kao u nastavku.

@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()); } 

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

Lažni objekti koriste se za provjeru ponašanja objekata tijekom testa. Pod ponašanjem objekta mislim da provjeravamo jesu li ispravne metode i putanje izvršene na objektu prilikom pokretanja testa.

To se uvelike razlikuje od sporedne uloge klina koji se koristi za postizanje rezultata onome što testirate.

U kalupu koristimo obrazac definiranja povratne vrijednosti za metodu.

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

U lažnoj provjeri ponašanja predmeta pomoću sljedećeg oblika.

verify(listMock).add(s); 

Here is a simple example where we want to test that a new trade is audited correctly.

Here is the main code.

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; } 

The test below creates a stub for the trade repository and mock for the AuditService

We then call verify on the mocked AuditService to make sure that the TradeService calls it's

logNewTrade method correctly

@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); } 

The following line does the checking on the mocked AuditService.

verify(auditService).logNewTrade(trade);

This test allows us to show that the audit service behaves correctly when creating a trade.

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

It's worth having a look at the above link for the strict definition of a Test Spy.

However in Mockito I like to use it to allow you to wrap a real object and then verify or modify it's behaviour to support your testing.

Here is an example were we check the standard behaviour of a List. Note that we can both verify that the add method is called and also assert that the item was added to the list.

@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()); } 

Compare this with using a mock object where only the method call can be validated. Because we only mock the behaviour of the list, it does not record that the item has been added and returns the default value of zero when we call the size() method.

@Mock List listMock = new ArrayList(); @Test public void testMockReturnsZero() throws Exception { String s = "dobie"; listMock.add(new String(s)); verify(listMock).add(s); assertEquals(0, listMock.size()); } 

Another useful feature of the testSpy is the ability to stub return calls. When this is done the object will behave as normal until the stubbed method is called.

In this example we stub the get method to always throw a RuntimeException. The rest of the behaviour remains the same.

@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); } 

In this example we again keep the core behaviour but change the size() method to return 1 initially and 5 for all subsequent calls.

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); } 

This is pretty Magic!

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

Lažni predmeti obično su ručno izrađeni ili lagani predmeti koji se koriste samo za ispitivanje i nisu pogodni za proizvodnju. Dobar primjer bi bila baza podataka u memoriji ili lažni servisni sloj.

Oni imaju tendenciju pružiti puno više funkcionalnosti od standardnih testnih parova i kao takvi vjerojatno nisu obično kandidati za implementaciju pomoću Mockita. To ne znači da ih se nije moglo konstruirati kao takve, već da se vjerojatno ne isplati provoditi na ovaj način.

Isprobajte dvostruke uzorke

Endo-testiranje: Jedinstveno testiranje s lažnim objektima

Lažne uloge, a ne predmeti

Ismijavanje nije zakrčeno

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

Ovu priču "Mocks And Stubs - Understanding Test Doubles With Mockito" izvorno je objavio JavaWorld.