Mocks And Stubs - Verstehen von Testdoppeln mit Mockito

Eine häufige Sache, auf die ich stoße, ist, dass Teams, die ein Spott-Framework verwenden, davon ausgehen, dass sie sich verspotten.

Sie wissen nicht, dass Mocks nur eines von mehreren 'Test Doubles' sind, die Gerard Meszaros bei xunitpatterns.com kategorisiert hat.

Es ist wichtig zu wissen, dass jeder Testdoppeltyp beim Testen eine andere Rolle spielt. Genauso wie Sie verschiedene Muster oder Refactoring lernen müssen, müssen Sie die primitiven Rollen der einzelnen Testdoppeltypen verstehen. Diese können dann kombiniert werden, um Ihre Testanforderungen zu erfüllen.

Ich werde einen sehr kurzen Überblick darüber geben, wie diese Klassifizierung zustande kam und wie sich die einzelnen Typen unterscheiden.

Ich werde dies anhand einiger kurzer, einfacher Beispiele in Mockito tun.

Seit Jahren schreiben Menschen leichtgewichtige Versionen von Systemkomponenten, um beim Testen zu helfen. Im Allgemeinen wurde es Stubbing genannt. Im Jahr 2000 führte der Artikel 'Endo-Testing: Unit Testing with Mock Objects' das Konzept eines Mock Object ein. Seitdem wurden Stubs, Mocks und eine Reihe anderer Arten von Testobjekten von Meszaros als Testdoppel klassifiziert.

Diese Terminologie wurde von Martin Fowler in "Mocks Aren't Stubs" erwähnt und wird in der Microsoft-Community übernommen, wie in "Exploring The Continuum of Test Doubles" gezeigt.

Ein Link zu jedem dieser wichtigen Dokumente finden Sie im Referenzabschnitt.

Das obige Diagramm zeigt die häufig verwendeten Testdoppeltypen. Die folgende URL enthält einen guten Querverweis auf jedes der Muster und ihre Funktionen sowie eine alternative Terminologie.

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

Mockito ist ein Testspion-Framework und sehr einfach zu erlernen. Bemerkenswert bei Mockito ist, dass die Erwartungen an Scheinobjekte vor dem Test nicht definiert werden, wie dies manchmal in anderen Verspottungsframeworks der Fall ist. Dies führt zu einem natürlicheren Stil (IMHO), wenn mit dem Verspotten begonnen wird.

Die folgenden Beispiele dienen lediglich der einfachen Demonstration der Verwendung von Mockito zur Implementierung der verschiedenen Arten von Testdoppel.

Auf der Website gibt es eine viel größere Anzahl spezifischer Beispiele für die Verwendung von Mockito.

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

Im Folgenden finden Sie einige grundlegende Beispiele für die Verwendung von Mockito, um die von Meszaros definierte Rolle jedes Testdoppels zu zeigen.

Ich habe jeweils einen Link zur Hauptdefinition eingefügt, damit Sie weitere Beispiele und eine vollständige Definition erhalten.

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

Dies ist das einfachste aller Testdoppel. Dies ist ein Objekt ohne Implementierung, das lediglich zum Auffüllen von Argumenten von Methodenaufrufen verwendet wird, die für Ihren Test irrelevant sind.

Im folgenden Code wird beispielsweise viel Code verwendet, um den Kunden zu erstellen, was für den Test nicht wichtig ist.

Dem Test ist es egal, welcher Kunde hinzugefügt wird, solange die Anzahl der Kunden wieder eins ist.

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

Der Inhalt des Kundenobjekts ist uns eigentlich egal - aber es ist erforderlich. Wir können einen Nullwert versuchen, aber wenn der Code korrekt ist, würden Sie erwarten, dass eine Art Ausnahme ausgelöst wird.

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

Um dies zu vermeiden, können wir einen einfachen Mockito-Dummy verwenden, um das gewünschte Verhalten zu erzielen.

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

Es ist dieser einfache Code, der ein Dummy-Objekt erstellt, das an den Aufruf übergeben wird.

Customer dummy = mock(Customer.class);

Lassen Sie sich nicht von der Mock-Syntax täuschen - die Rolle, die hier gespielt wird, ist die eines Dummys, nicht eines Mocks.

Es ist die Rolle des Testdoppels, die es auszeichnet, nicht die Syntax, mit der es erstellt wird.

Diese Klasse dient als einfacher Ersatz für die Kundenklasse und macht den Test sehr einfach zu lesen.

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

Die Rolle des Teststubs besteht darin, kontrollierte Werte an das zu testende Objekt zurückzugeben. Diese werden als indirekte Eingaben in den Test beschrieben. Hoffentlich verdeutlicht ein Beispiel, was dies bedeutet.

Nehmen Sie den folgenden Code

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

Der SimplePricingService verfügt über ein zusammenarbeitendes Objekt, das das Handelsrepository ist. Das Handelsrepository stellt dem Preisservice über die Methode getPriceForTrade Handelspreise zur Verfügung.

Damit wir die Businees-Logik im SimplePricingService testen können, müssen wir diese indirekten Eingaben steuern

dh Eingaben, die wir nie in den Test bestanden haben.

Dies ist unten gezeigt.

Im folgenden Beispiel stubben wir das PricingRepository, um bekannte Werte zurückzugeben, mit denen die Geschäftslogik des SimpleTradeService getestet werden kann.

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

Saboteur Beispiel

Es gibt zwei gängige Varianten von Teststubs: Responder und Saboteur.

Responder werden verwendet, um den glücklichen Pfad wie im vorherigen Beispiel zu testen.

Ein Saboteur wird verwendet, um außergewöhnliches Verhalten wie folgt zu testen.

@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

Scheinobjekte werden verwendet, um das Objektverhalten während eines Tests zu überprüfen. Mit Objektverhalten meine ich, dass wir überprüfen, ob die richtigen Methoden und Pfade für das Objekt ausgeführt werden, wenn der Test ausgeführt wird.

Dies unterscheidet sich stark von der unterstützenden Rolle eines Stubs, der verwendet wird, um Ergebnisse für alles zu liefern, was Sie testen.

In einem Stub verwenden wir das Muster, einen Rückgabewert für eine Methode zu definieren.

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

In einem Mock überprüfen wir das Verhalten des Objekts mit dem folgenden Formular.

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

Gefälschte Objekte sind normalerweise handgefertigte oder leichte Objekte, die nur zum Testen verwendet werden und nicht für die Produktion geeignet sind. Ein gutes Beispiel wäre eine In-Memory-Datenbank oder eine gefälschte Serviceschicht.

Sie bieten tendenziell viel mehr Funktionen als Standard-Test-Doubles und sind daher wahrscheinlich keine Kandidaten für die Implementierung mit Mockito. Das heißt nicht, dass sie nicht als solche konstruiert werden könnten, nur dass es sich wahrscheinlich nicht lohnt, sie auf diese Weise zu implementieren.

Testen Sie doppelte Muster

Endo-Testing: Unit-Testing mit Mock-Objekten

Scheinrollen, keine Objekte

Mocks sind keine Stubs

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

Diese Geschichte "Mocks And Stubs - Test Doubles With Mockito verstehen" wurde ursprünglich von JavaWorld veröffentlicht.