creating mocks spies mockito with code examples
Výukový program Mockito Spy and Mocks:
V tomhle Série výukových programů Mockito , náš předchozí tutoriál nám dal Úvod do rámce Mockito . V tomto tutoriálu se naučíme koncept Mocks and Spies in Mockito.
Co jsou Mocks and Spies?
Mocks i Spies jsou typy testovacích dvojic, které jsou užitečné při psaní testů jednotek.
Mocks jsou úplnou náhradou za závislost a mohou být naprogramovány tak, aby vrátily zadaný výstup, kdykoli se zavolá metoda na falešném. Mockito poskytuje výchozí implementaci pro všechny falešné metody.
Co se naučíte:
- Co jsou špióni?
- Vytváření Mocks
- Vytváření špiónů
- Jak aplikovat falešné závislosti pro testovanou třídu / objekt?
- tipy a triky
- Příklady kódu - Spies & Mocks
- Zdrojový kód
- Doporučené čtení
Co jsou špióni?
Špioni jsou v podstatě obalem skutečné instance zesměšňované závislosti. To znamená, že vyžaduje novou instanci Object nebo závislosti a poté nad ni přidá obálku zesměšňovaného objektu. Ve výchozím nastavení Spies volá skutečné metody objektu, pokud není ukraden.
Špioni poskytují určité další pravomoci, například jaké argumenty byly dodány volání metody, byla vůbec nazývána skutečná metoda atd.
Stručně řečeno, pro Spiese:
- Je vyžadována skutečná instance objektu.
- Spies poskytuje flexibilitu k zamezení některých (nebo všech) metod sledovaného objektu. V té době je špión v podstatě nazýván nebo odkazován na částečně zesměšňovaný nebo zastřený objekt.
- Interakce vyvolané sledovaným objektem lze sledovat a ověřit.
Obecně platí, že Spies se nepoužívají příliš často, ale mohou být užiteční pro starší aplikace testování jednotek, kde závislosti nelze zcela zesměšňovat.
Veškerý popis Mock and Spy odkazujeme na fiktivní třídu / objekt s názvem „DiscountCalculator“, kterou chceme zesměšňovat / špehovat.
Má některé metody, jak je uvedeno níže:
vypočítatSlevu - Vypočítá zlevněnou cenu daného produktu.
getDiscountLimit - Načte horní limit limitu slevy pro produkt.
Vytváření Mocks
# 1) Mock tvorba s kódem
Mockito dává několik přetížených verzí Mockita. Mocks metoda a umožňuje vytváření falešných závislostí.
Syntax:
Mockito.mock(Class classToMock)
Příklad:
Předpokládejme, že název třídy je DiscountCalculator, k vytvoření falešného kódu:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Je důležité si uvědomit, že Mock lze vytvořit jak pro rozhraní, tak pro konkrétní třídu.
Když je objekt zesměšňován, pokud nejsou všechny metody vráceny ve výchozím nastavení null .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Mock tvorba s anotacemi
Místo posměchu pomocí statické metody „mock“ v knihovně Mockito poskytuje také zkratkový způsob vytváření falešných zpráv pomocí anotace „@Mock“.
Největší výhodou tohoto přístupu je, že je jednoduchý a umožňuje kombinovat deklaraci a v podstatě inicializaci. Zlepšuje také čitelnost testů a zamezuje opakované inicializaci falešných zpráv, když se na několika místech používá stejný falešný test.
Aby byla zajištěna inicializace Mock prostřednictvím tohoto přístupu, je nutné, abychom pro testovanou třídu zavolali „MockitoAnnotations.initMocks (this)“. To je ideální kandidát na to, aby se stal součástí metody „beforeEach“ Junitu, která zajišťuje, že se falešné zprávy inicializují pokaždé, když se z dané třídy provede test.
Syntax:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Vytváření špiónů
Podobně jako Mocks, Spies lze také vytvořit dvěma způsoby:
# 1) Tvorba špionů s kódem
Mockito.spy je statická metoda, která se používá k vytvoření „špionážního“ objektu / obálky kolem instance skutečného objektu.
Syntax:
co je nejlepší mp3 downloader pro Android
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Tvorba špionů s anotacemi
Podobně jako Mock, Spies lze vytvořit pomocí anotace @Spy.
Také pro inicializaci Spy musíte zajistit, aby MockitoAnnotations.initMocks (toto) byly volány před použitím Spy ve skutečném testu, aby se spy inicializoval.
Syntax:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Jak aplikovat falešné závislosti pro testovanou třídu / objekt?
Když chceme vytvořit falešný objekt testované třídy s ostatními falešnými závislostmi, můžeme použít anotaci @InjectMocks.
To v podstatě dělá to, že všechny objekty označené anotacemi @Mock (nebo @Spy) jsou vloženy jako injekce dodavatele nebo vlastnosti do třídy Object a poté lze ověřit interakce na konečném Mocked objektu.
Opět je zbytečné zmínit, @InjectMocks je zkratka proti vytvoření nového Object třídy a poskytuje zesměšňované objekty závislostí.
Rozumíme tomu příkladem:
Předpokládejme, že existuje třída PriceCalculator, která má DiscountCalculator a UserService jako závislosti, které jsou vkládány prostřednictvím polí konstruktoru nebo vlastnosti.
Abychom mohli vytvořit implementaci Mocked pro třídu kalkulačky ceny, můžeme použít 2 přístupy:
# 1) Vytvořit nová instance PriceCalculator a injektovat Mocked závislostí
ve vaší síti jste nahradili výchozí bránu
@Mock private transient DiscountCalculator mockedDiscountCalculator; @Mock private transient UserService userService; @Mock private transient ItemService mockedItemService; private transient PriceCalculator priceCalculator; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); priceCalculator = new PriceCalculator(mockedDiscountCalculator, userService, mockedItemService); }
# 2) Vytvořit zesměšňovaná instance PriceCalculatoru a vkládání závislostí prostřednictvím poznámky @InjectMocks
@Mock private transient DiscountCalculator mockedDiscountCalculator; @Mock private transient UserService userService; @Mock private transient ItemService mockedItemService; @InjectMocks private transient PriceCalculator priceCalculator; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this);
Anotace InjectMocks se ve skutečnosti pokouší injektovat zesměšněné závislosti pomocí jednoho z níže uvedených přístupů:
- Vstřikování na základě konstruktéra - Využívá konstruktor pro testovanou třídu.
- Metody seteru založené - Když tam není konstruktor, Mockito se pokusí injektovat pomocí nastavovačů vlastností.
- Field Based - Pokud výše uvedené 2 nejsou k dispozici, pokusí se přímo provést injekci prostřednictvím polí.
tipy a triky
# 1) Nastavení různých pahýlů pro různá volání stejnou metodou:
Když je uvnitř testované metody vícekrát volána stubbed metoda (nebo je stubbed metoda ve smyčce a chcete pokaždé vrátit jiný výstup), můžete nastavit Mock tak, aby pokaždé vrátila odlišnou stubbed odezvu.
Například: Předpokládejme, že chcete Služba položky vrátit jinou položku pro 3 po sobě jdoucí volání a máte položky deklarované ve své metodě v rámci testů jako Item1, Item2 a Item3, pak je můžete jednoduše vrátit pro 3 po sobě následující vyvolání pomocí níže uvedeného kódu:
@Test public void calculatePrice_withCorrectInput_returnsValidResult() { // Arrange ItemSku item1 = new ItemSku(); ItemSku item2 = new ItemSku(); ItemSku item3 = new ItemSku(); // Setup Mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1, item2, item3); // Assert //TODO - add assert statements }
#dva) Vyvolání výjimky pomocí Mock: Toto je velmi běžný scénář, když chcete otestovat / ověřit downstream / závislost vyvolávající výjimku a zkontrolovat chování testovaného systému. Chcete-li však vyvolat výjimku Mock, budete muset nastavit stub pomocí thenThrow.
@Test public void calculatePrice_withInCorrectInput_throwsException() { // Arrange ItemSku item1 = new ItemSku(); // Setup Mocks when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Assert //TODO - add assert statements }
U zápasů jako anyInt () a anyString () se nenechte zastrašit, protože se jimi budou zabývat nadcházející články. V zásadě vám ale dávají flexibilitu poskytnout libovolnou hodnotu Integer a String bez konkrétních argumentů funkce.
Příklady kódu - Spies & Mocks
Jak již bylo zmíněno dříve, jak Spies, tak Mocks jsou typem testovací dvojice a mají své vlastní zvyklosti.
Zatímco špioni jsou užiteční pro testování starších aplikací (a tam, kde nejsou možné falešné zprávy), pro všechny ostatní pěkně napsané testovatelné metody / třídy postačuje Mocks většině potřeb testování Unit.
Pro stejný příklad: Pojďme napsat test pomocí Mocks pro PriceCalculator -> metoda countPrice (metoda vypočítá itemPrice po odečtení příslušných slev)
PriceCalculator třída a testovaná metoda CalcPrice vypadá takto:
public class PriceCalculator { public DiscountCalculator discountCalculator; public UserService userService; public ItemService itemService; public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService) { this.discountCalculator = discountCalculator; this.userService = userService; this.itemService = itemService; } public double calculatePrice(int itemSkuCode, int customerAccountId) { double price = 0; // get Item details ItemSku sku = itemService.getItemDetails(itemSkuCode); // get User and calculate price CustomerProfile customerProfile = userService.getUser(customerAccountId); double basePrice = sku.getPrice(); price = basePrice - (basePrice* (sku.getApplicableDiscount() + customerProfile.getExtraLoyaltyDiscountPercentage())/100); return price; } }
Nyní pro tuto metodu napíšeme pozitivní test.
Chystáme se zakázat službu userService a položku, jak je uvedeno níže:
- UserService vždy vrátí CustomerProfile s loyaltyDiscountPercentage nastaveným na 2.
- ItemService vždy vrátí položku se základní cenou 100 a příslušnou slevou 5.
- S výše uvedenými hodnotami očekávaná cena vrácená testovanou metodou vyjde na 93 $.
Zde je kód pro test:
@Test public void calculatePrice_withCorrectInput_returnsExpectedPrice() { // Arrange ItemSku item1 = new ItemSku(); item1.setApplicableDiscount(5.00); item1.setPrice(100.00); CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 93.00; // Setting up stubbed responses using mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1); when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(123,5432); // Assert assertEquals(expectedPrice, actualPrice); }
Jak můžete vidět, ve výše uvedeném testu - Tvrdíme, že skutečná cena vrácená metodou se rovná očekávané ceně, tj. 93,00.
Pojďme nyní napsat test pomocí Spy.
Budeme špehovat ItemService a budeme kódovat implementaci ItemService takovým způsobem, že vždy vrátí položku s basePrice 200 a applicableDiscount 10,00% (zbytek falešného nastavení zůstává stejný), kdykoli je volána s skuCode 2367.
@InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Spy private ItemService mockedItemService = new ItemServiceImpl(); @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(2367,5432); // Assert assertEquals(expectedPrice, actualPrice);
Nyní se podívejme Příklad výjimky vyvolané ItemService, protože dostupné množství položky bylo 0. Nastavíme falešnou výjimku.
@InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Mock private ItemService mockedItemService = new ItemServiceImpl(); @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_whenItemNotAvailable_throwsException() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Act & Assert assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234)); }
Na výše uvedených příkladech jsem se pokusil vysvětlit koncept Mocks & Spies a jak je lze kombinovat za účelem vytvoření efektivních a užitečných testů jednotek.
Může existovat několik kombinací těchto technik k získání sady testů, které zvyšují pokrytí testované metody, čímž zajišťují vysokou úroveň důvěry v kód a zvyšují odolnost kódu proti regresním chybám.
Zdrojový kód
Rozhraní
DiscountCalculator
public interface DiscountCalculator { double calculateDiscount(ItemSku itemSku, double markedPrice); void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile); }
Služba položky
public interface ItemService { ItemSku getItemDetails(int skuCode) throws ItemServiceException; }
UserService
public interface UserService { void addUser(CustomerProfile customerProfile); void deleteUser(CustomerProfile customerProfile); CustomerProfile getUser(int customerAccountId); }
Implementace rozhraní
DiscountCalculatorImpl
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
ItemServiceImpl
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
Modely
Profil zákazníka
public class CustomerProfile { private String customerName; private String loyaltyTier; private String customerAddress; private String accountId; private double extraLoyaltyDiscountPercentage; public double getExtraLoyaltyDiscountPercentage() { return extraLoyaltyDiscountPercentage; } public void setExtraLoyaltyDiscountPercentage(double extraLoyaltyDiscountPercentage) { this.extraLoyaltyDiscountPercentage = extraLoyaltyDiscountPercentage; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } public String getLoyaltyTier() { return loyaltyTier; } public void setLoyaltyTier(String loyaltyTier) { this.loyaltyTier = loyaltyTier; } public String getCustomerAddress() { return customerAddress; } public void setCustomerAddress(String customerAddress) { this.customerAddress = customerAddress; } }
ItemSku
public class ItemSku { private int skuCode; private double price; private double maxDiscount; private double margin; private int totalQuantity; private double applicableDiscount; public double getApplicableDiscount() { return applicableDiscount; } public void setApplicableDiscount(double applicableDiscount) { this.applicableDiscount = applicableDiscount; } public int getTotalQuantity() { return totalQuantity; } public void setTotalQuantity(int totalQuantity) { this.totalQuantity = totalQuantity; } public int getSkuCode() { return skuCode; } public void setSkuCode(int skuCode) { this.skuCode = skuCode; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getMaxDiscount() { return maxDiscount; } public void setMaxDiscount(double maxDiscount) { this.maxDiscount = maxDiscount; } public double getMargin() { return margin; } public void setMargin(double margin) { this.margin = margin; } }
Testovaná třída - PriceCalculator
public class PriceCalculator { public DiscountCalculator discountCalculator; public UserService userService; public ItemService itemService; public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService){ this.discountCalculator = discountCalculator; this.userService = userService; this.itemService = itemService; } public double calculatePrice(int itemSkuCode, int customerAccountId) { double price = 0; // get Item details ItemSku sku = itemService.getItemDetails(itemSkuCode); // get User and calculate price CustomerProfile customerProfile = userService.getUser(customerAccountId); double basePrice = sku.getPrice(); price = basePrice - (basePrice* (sku.getApplicableDiscount() + customerProfile.getExtraLoyaltyDiscountPercentage())/100); return price; } }
Testy jednotek - PriceCalculatorUnitTests
public class PriceCalculatorUnitTests { @InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Mock private ItemService mockedItemService; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_withCorrectInput_returnsExpectedPrice() { // Arrange ItemSku item1 = new ItemSku(); item1.setApplicableDiscount(5.00); item1.setPrice(100.00); CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 93.00; // Setting up stubbed responses using mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1); when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(123,5432); // Assert assertEquals(expectedPrice, actualPrice); } @Test @Disabled // to enable this change the ItemService MOCK to SPY public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(2367,5432); // Assert assertEquals(expectedPrice, actualPrice); } @Test public void calculatePrice_whenItemNotAvailable_throwsException() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Act & Assert assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234)); } }
Různé typy Matcherů, které poskytuje Mockito, jsou vysvětleny v našem nadcházejícím tutoriálu.
Výukový program PREV | DALŠÍ výuka
Doporučené čtení
- Různé typy Matchers poskytované Mockito
- Výukový program Mockito: Rámec Mockito pro zesměšňování při testování jednotek
- Vytváření testů epoch pomocí epoch Studio pro Eclipse
- Výukový program Python DateTime s příklady
- Vyjmout příkaz v Unixu s příklady
- Syntaxe příkazů Unix Cat, možnosti s příklady
- Použití kurzoru v MongoDB s příklady
- Příkaz LS v Unixu s příklady