Mocks And Stubs - Memahami Tes Ganda Dengan Mockito

Hal umum yang saya temui adalah bahwa tim yang menggunakan kerangka kerja mengejek menganggap mereka mengejek.

Mereka tidak menyadari bahwa Mocks hanyalah salah satu dari sejumlah 'Uji Ganda' yang telah dikategorikan Gerard Meszaros di xunitpatterns.com.

Penting untuk disadari bahwa setiap jenis tes ganda memiliki peran berbeda untuk dimainkan dalam pengujian. Dengan cara yang sama seperti Anda perlu mempelajari pola atau pemfaktoran ulang yang berbeda, Anda perlu memahami peran primitif dari setiap jenis pengujian ganda. Ini kemudian dapat digabungkan untuk mencapai kebutuhan pengujian Anda.

Saya akan membahas sejarah yang sangat singkat tentang bagaimana klasifikasi ini terjadi, dan bagaimana masing-masing jenis berbeda.

Saya akan melakukan ini menggunakan beberapa contoh singkat dan sederhana di Mockito.

Selama bertahun-tahun orang telah menulis versi ringan dari komponen sistem untuk membantu pengujian. Secara umum itu disebut stubbing. Pada tahun 2000, artikel 'Endo-Testing: Unit Testing dengan Mock Objects' memperkenalkan konsep Mock Object. Sejak itu Stubs, Mocks dan sejumlah jenis benda uji lainnya telah diklasifikasikan oleh Meszaros sebagai Uji Ganda.

Terminologi ini telah direferensikan oleh Martin Fowler di "Mocks Arn't Stubs" dan diadopsi dalam komunitas Microsoft seperti yang ditunjukkan dalam "Menjelajahi Kontinum Tes Ganda"

Tautan ke masing-masing makalah penting ini ditunjukkan di bagian referensi.

Diagram di atas menunjukkan jenis tes ganda yang umum digunakan. URL berikut memberikan referensi silang yang baik untuk setiap pola dan fiturnya serta terminologi alternatif.

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

Mockito adalah kerangka kerja mata-mata uji coba dan sangat mudah dipelajari. Yang menonjol dengan Mockito adalah bahwa ekspektasi dari objek tiruan apa pun tidak ditentukan sebelum pengujian karena terkadang ada dalam kerangka kerja tiruan lainnya. Ini mengarah ke gaya yang lebih alami (IMHO) saat mulai mengejek.

Contoh berikut di sini murni untuk memberikan demonstrasi sederhana menggunakan Mockito untuk mengimplementasikan berbagai jenis tes ganda.

Ada lebih banyak contoh spesifik tentang cara menggunakan Mockito di situs web.

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

Di bawah ini adalah beberapa contoh dasar menggunakan Mockito untuk menunjukkan peran dari setiap tes ganda seperti yang didefinisikan oleh Meszaros.

Saya telah menyertakan tautan ke definisi utama untuk masing-masing sehingga Anda bisa mendapatkan lebih banyak contoh dan definisi lengkap.

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

Ini adalah tes ganda yang paling sederhana dari semua tes ganda. Ini adalah objek yang tidak memiliki implementasi yang digunakan murni untuk mengisi argumen panggilan metode yang tidak relevan dengan pengujian Anda.

Misalnya, kode di bawah ini menggunakan banyak kode untuk membuat pelanggan yang tidak penting untuk pengujian.

Pengujian tidak peduli pelanggan mana yang ditambahkan, selama jumlah pelanggan kembali sebagai satu.

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

Kami sebenarnya tidak peduli dengan konten objek pelanggan - tetapi diperlukan. Kita dapat mencoba nilai null, tetapi jika kodenya benar, Anda akan mengharapkan semacam pengecualian untuk dilemparkan.

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

Untuk menghindari hal ini kita bisa menggunakan boneka Mockito sederhana untuk mendapatkan perilaku yang diinginkan.

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

Kode sederhana inilah yang membuat objek dummy untuk diteruskan ke panggilan.

Customer dummy = mock(Customer.class);

Jangan tertipu oleh sintaks tiruan - peran yang dimainkan di sini adalah dummy, bukan tiruan.

Peran test double yang membedakannya, bukan sintaks yang digunakan untuk membuatnya.

Kelas ini berfungsi sebagai pengganti sederhana untuk kelas pelanggan dan membuat tes sangat mudah dibaca.

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

Peran stub pengujian adalah mengembalikan nilai yang dikontrol ke objek yang diuji. Ini dijelaskan sebagai input tidak langsung untuk pengujian. Semoga sebuah contoh akan menjelaskan apa artinya ini.

Ambil kode berikut

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 memiliki satu objek kolaborasi yang merupakan gudang perdagangan. Repositori perdagangan memberikan harga perdagangan ke layanan penetapan harga melalui metode getPriceForTrade.

Agar kami dapat menguji logika businees di SimplePricingService, kami perlu mengontrol input tidak langsung ini

yaitu masukan yang tidak pernah kami lewati untuk ujian.

Ini ditunjukkan di bawah.

Dalam contoh berikut, kami menghentikan PricingRepository untuk mengembalikan nilai yang diketahui yang dapat digunakan untuk menguji logika bisnis 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()); } 

Contoh Penyabot

Ada 2 varian umum dari Test Stubs: Responder's dan Saboteur's.

Responder digunakan untuk menguji jalan bahagia seperti pada contoh sebelumnya.

Seorang penyabot digunakan untuk menguji perilaku luar biasa seperti di bawah ini.

@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

Objek tiruan digunakan untuk memverifikasi perilaku objek selama pengujian. Yang saya maksud dengan perilaku objek adalah kami memeriksa bahwa metode dan jalur yang benar dieksekusi pada objek saat pengujian dijalankan.

Ini sangat berbeda dengan peran pendukung sebuah rintisan yang digunakan untuk memberikan hasil pada apa pun yang Anda uji.

Dalam rintisan kita menggunakan pola mendefinisikan nilai kembalian untuk sebuah metode.

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

Dalam tiruan kami memeriksa perilaku objek menggunakan formulir berikut.

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

Benda palsu biasanya buatan tangan atau benda ringan yang hanya digunakan untuk pengujian dan tidak cocok untuk produksi. Contoh yang bagus adalah database dalam memori atau lapisan layanan palsu.

Mereka cenderung menyediakan lebih banyak fungsionalitas daripada tes ganda standar dan karena itu mungkin biasanya bukan kandidat untuk implementasi menggunakan Mockito. Itu tidak berarti bahwa mereka tidak dapat dibangun seperti itu, hanya saja mungkin tidak ada gunanya menerapkan cara ini.

Uji Pola Ganda

Endo-Testing: Pengujian Unit dengan Objek Mock

Peran Mock, Bukan Objek

Mengolok-olok Bukankah Stubs

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

Cerita ini, "Mocks And Stubs - Memahami Tes Ganda Dengan Mockito" aslinya diterbitkan oleh JavaWorld.