Mock And Stubs - Capire i doppi test con Mockito

Una cosa comune in cui mi imbatto è che i team che utilizzano un framework beffardo presumono di essere beffardi.

Non sono consapevoli del fatto che i mock sono solo uno dei numerosi "Test Doubles" che Gerard Meszaros ha classificato su xunitpatterns.com.

È importante rendersi conto che ogni tipo di doppio test ha un ruolo diverso da svolgere nei test. Nello stesso modo in cui hai bisogno di imparare diversi modelli o refactoring, devi capire i ruoli primitivi di ogni tipo di doppio di prova. Questi possono quindi essere combinati per soddisfare le tue esigenze di test.

Tratterò una brevissima storia di come è nata questa classificazione e di come ciascuno dei tipi differisce.

Lo farò usando alcuni brevi e semplici esempi in Mockito.

Per anni le persone hanno scritto versioni leggere dei componenti di sistema per aiutare con i test. In generale si chiamava stubbing. Nel 2000 "l'articolo" Endo-Testing: Unit Testing with Mock Objects "introduceva il concetto di Mock Object. Da allora, Meszaros ha classificato Stub, Mock e una serie di altri tipi di oggetti di test come Test Double.

Questa terminologia è stata citata da Martin Fowler in "Mocks Aren't Stubs" e viene adottata dalla comunità Microsoft, come mostrato in "Exploring The Continuum of Test Double"

Un collegamento a ciascuno di questi documenti importanti è mostrato nella sezione di riferimento.

Il diagramma sopra mostra i tipi di test double comunemente usati. Il seguente URL fornisce un buon riferimento incrociato a ciascuno dei modelli e alle loro caratteristiche, nonché alla terminologia alternativa.

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

Mockito è un framework spia di prova ed è molto semplice da imparare. Notevole con Mockito è che le aspettative di qualsiasi oggetto fittizio non sono definite prima del test, come a volte lo sono in altri quadri beffardi. Questo porta a uno stile più naturale (IMHO) quando si inizia a prendere in giro.

I seguenti esempi sono qui puramente per fornire una semplice dimostrazione dell'utilizzo di Mockito per implementare i diversi tipi di doppioni di test.

Ci sono un numero molto maggiore di esempi specifici di come utilizzare Mockito sul sito web.

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

Di seguito sono riportati alcuni esempi di base che utilizzano Mockito per mostrare il ruolo di ogni doppio di prova come definito da Meszaros.

Ho incluso un collegamento alla definizione principale per ciascuno in modo da poter ottenere più esempi e una definizione completa.

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

Questo è il più semplice di tutti i doppi di prova. Questo è un oggetto che non ha implementazione che viene utilizzato esclusivamente per popolare argomenti di chiamate di metodo che sono irrilevanti per il test.

Ad esempio, il codice seguente utilizza molto codice per creare il cliente che non è importante per il test.

Al test non potrebbe importare di meno quale cliente viene aggiunto, purché il conteggio dei clienti ritorni come uno.

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

In realtà non ci interessa il contenuto dell'oggetto cliente, ma è obbligatorio. Possiamo provare un valore nullo, ma se il codice è corretto ti aspetteresti che venga generata una sorta di eccezione.

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

Per evitare ciò possiamo utilizzare un semplice manichino Mockito per ottenere il comportamento desiderato.

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

È questo semplice codice che crea un oggetto fittizio da passare alla chiamata.

Customer dummy = mock(Customer.class);

Non lasciarti ingannare dalla finta sintassi: il ruolo che viene interpretato qui è quello di un manichino, non di uno scherzo.

È il ruolo del doppio di prova che lo distingue, non la sintassi utilizzata per crearne uno.

Questa classe funziona come un semplice sostituto della classe del cliente e rende il test molto facile da leggere.

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

Il ruolo dello stub di test è restituire valori controllati all'oggetto da testare. Questi sono descritti come input indiretti al test. Si spera che un esempio chiarisca cosa significa.

Prendi il codice seguente

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

Il SimplePricingService ha un oggetto collaborativo che è il repository di dati sulle negoziazioni. Il repository di dati sulle negoziazioni fornisce i prezzi di negoziazione al servizio di determinazione dei prezzi tramite il metodo getPriceForTrade.

Per poter testare la logica aziendale nel SimplePricingService, dobbiamo controllare questi input indiretti

cioè input che non abbiamo mai passato al test.

Questo è mostrato di seguito.

Nell'esempio seguente abbiamo bloccato PricingRepository per restituire valori noti che possono essere utilizzati per testare la logica di business di 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()); } 

Esempio di sabotatore

Esistono 2 varianti comuni di Test Stub: Responder's e Saboteur's.

I risponditori vengono utilizzati per testare il percorso felice come nell'esempio precedente.

Un sabotatore viene utilizzato per testare un comportamento eccezionale come di seguito.

@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

Gli oggetti fittizi vengono utilizzati per verificare il comportamento degli oggetti durante un test. Per comportamento dell'oggetto intendo che controlliamo che i metodi e i percorsi corretti siano esercitati sull'oggetto quando viene eseguito il test.

Questo è molto diverso dal ruolo di supporto di uno stub che viene utilizzato per fornire risultati a qualsiasi cosa tu stia testando.

In uno stub usiamo il modello di definizione di un valore di ritorno per un metodo.

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

In una simulazione controlliamo il comportamento dell'oggetto utilizzando il seguente modulo.

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

Gli oggetti falsi sono solitamente oggetti fatti a mano o leggeri utilizzati solo per test e non adatti alla produzione. Un buon esempio potrebbe essere un database in memoria o un falso livello di servizio.

Tendono a fornire molte più funzionalità rispetto ai doppi di test standard e come tali probabilmente non sono solitamente candidati per l'implementazione utilizzando Mockito. Questo non vuol dire che non possano essere costruiti come tali, solo che probabilmente non vale la pena implementarli in questo modo.

Prova doppi modelli

Endo-Testing: unit test con oggetti fittizi

Ruoli fittizi, non oggetti

I mock non sono mozziconi

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

Questa storia, "Mocks And Stubs - Understanding Test Double With Mockito" è stata originariamente pubblicata da JavaWorld.