creating mocks spies mockito with code examples
Tutorial Mockito Spy and Mocks:
In acest Seria Tutorial Mockito , tutorialul nostru anterior ne-a oferit un Introducere în Mockito Framework . În acest tutorial, vom învăța conceptul de batjocuri și spioni în Mockito.
Ce sunt batjocoritorii și spionii?
Atât Mock-urile, cât și Spionii sunt tipurile de teste duble, care sunt utile în scrierea testelor unitare.
Mock-urile sunt un înlocuitor complet pentru dependență și pot fi programate pentru a returna ieșirea specificată ori de câte ori este apelată o metodă de pe mock. Mockito oferă o implementare implicită pentru toate metodele unui simulator.
Ce veți învăța:
- Ce sunt spionii?
- Crearea batjocurilor
- Crearea spionilor
- Cum se injectează dependențe batjocurate pentru clasa / obiectul testat?
- Sfaturi si trucuri
- Exemple de cod - Spioni și batjocuri
- Cod sursa
- Lectură recomandată
Ce sunt spionii?
Spionii sunt, în esență, o învelitoare a unei instanțe reale a dependenței batjocorite. Ceea ce înseamnă acest lucru este că necesită o nouă instanță a obiectului sau a dependenței și apoi adaugă un înveliș al obiectului batjocorit peste el. În mod implicit, spionii apelează la metode reale ale obiectului, cu excepția cazului în care sunt împiedicate.
Spionii oferă anumite puteri suplimentare, cum ar fi argumentele care au fost furnizate apelului metodei, a fost metoda reală apelată, etc.
Pe scurt, pentru Spioni:
- Este necesară instanța reală a obiectului.
- Spionii oferă flexibilitate pentru a împiedica unele (sau toate) metodele obiectului spionat. În acel moment, spionul este numit în mod esențial sau se referă la un obiect parțial batjocorit sau îndoit.
- Interacțiunile apelate la un obiect spionat pot fi urmărite pentru verificare.
În general, spionii nu sunt folosiți foarte frecvent, dar pot fi de ajutor pentru testarea unităților de aplicații vechi în care dependențele nu pot fi complet batjocurate.
Pentru toată descrierea Mock and Spy, ne referim la o clasă / obiect fictiv numit „DiscountCalculator” pe care vrem să-l batjocorim / spionăm.
Are câteva metode, așa cum se arată mai jos:
calculateDiscount - Calculează prețul redus al unui produs dat.
getDiscountLimit - Preluează limita superioară de reducere pentru produs.
Crearea batjocurilor
# 1) Creație falsă cu Cod
Mockito oferă mai multe versiuni supraîncărcate de Mockito. Metoda Mocks și permite crearea de mock-uri pentru dependențe.
Sintaxă:
Mockito.mock(Class classToMock)
Exemplu:
Să presupunem că numele clasei este DiscountCalculator, pentru a crea un simulator în cod:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Este important să rețineți că Mock poate fi creat atât pentru interfață, cât și pentru o clasă concretă.
Când un obiect este batjocorit, cu excepția cazului în care este stubed, toate metodele returnează nul în mod implicit .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Creație falsă cu adnotări
În loc să batjocorească folosind metoda statică „batjocură” a bibliotecii Mockito, oferă, de asemenea, o modalitate de scurtare de a crea batjocuri folosind adnotarea „@Babă”.
Cel mai mare avantaj al acestei abordări este că este simplu și permite combinarea declarației și inițializării. De asemenea, face testele mai lizibile și evită inițializarea repetată a batjocurilor atunci când aceeași batjocură este utilizată în mai multe locuri.
Pentru a asigura inițializarea Mock prin această abordare, este necesar să apelăm „MockitoAnnotations.initMocks (this)” pentru clasa supusă testului. Acesta este candidatul ideal pentru a face parte din metoda „beforeEach” a Junit, care asigură inițializarea jocurilor de fiecare dată când un test este executat din acea clasă.
Sintaxă:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Crearea spionilor
Similar cu Mock-urile, Spionii pot fi creați și în 2 moduri:
# 1) Crearea spionului cu cod
Mockito.spy este metoda statică care este utilizată pentru a crea un obiect „spion” în jurul instanței obiectului real.
Sintaxă:
ce este testarea de regresie în testarea software-ului
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Crearea spionajului cu adnotări
Similar cu Mock, spionii pot fi creați utilizând adnotarea @Spy.
Și pentru inițializarea Spy trebuie să vă asigurați că MockitoAnnotations.initMocks (aceasta) sunt apelate înainte ca Spy-ul să fie folosit în testul propriu-zis pentru a inițializa spionul.
Sintaxă:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Cum se injectează dependențe batjocurate pentru clasa / obiectul testat?
Când vrem să creăm un obiect simulat al clasei supuse testului cu celelalte dependențe batjocorite, putem folosi adnotarea @InjectMocks.
Ceea ce face în esență este că toate obiectele marcate cu adnotări @Mock (sau @Spy) sunt injectate ca Contractor sau injectare de proprietăți în clasa Object și apoi interacțiunile pot fi verificate pe obiectul Mocked final.
Din nou, inutil să menționăm, @InjectMocks este o prescurtare împotriva creării unui nou obiect al clasei și oferă obiecte batjocorite ale dependențelor.
Să înțelegem acest lucru cu un exemplu:
Să presupunem că există o clasă PriceCalculator, care are DiscountCalculator și UserService ca dependențe care sunt injectate prin câmpurile Constructor sau Property.
Deci, pentru a crea implementarea Mocked pentru clasa Calculator de prețuri, putem folosi 2 abordări:
# 1) Creați o nouă instanță a PriceCalculator și injectați dependențe Mocked
@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) Creați o instanță batjocorită a PriceCalculator și injectați dependențe prin adnotarea @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);
Adnotarea InjectMocks încearcă de fapt să injecteze dependențe batjocorite folosind una dintre abordările de mai jos:
- Injecție pe bază de constructor - Utilizează Constructor pentru clasa testată.
- Metode Setter Bazate - Când un constructor nu este acolo, Mockito încearcă să injecteze folosind setatori de proprietăți.
- Bazat pe câmp - Când cele 2 de mai sus nu sunt disponibile, atunci încearcă direct să injecteze prin câmpuri.
Sfaturi si trucuri
# 1) Configurarea stuburilor diferite pentru apeluri diferite ale aceleiași metode:
Când o metodă stubbed este apelată de mai multe ori în interiorul metodei testate (sau metoda stubbed este în buclă și doriți să returnați rezultate diferite de fiecare dată), puteți configura Mock pentru a returna de fiecare dată un răspuns stubbed diferit.
De exemplu: Să presupunem că vrei ItemService pentru a returna un articol diferit pentru 3 apeluri consecutive și aveți articole declarate în metoda dvs. sub teste ca Item1, Item2 și Item3, puteți pur și simplu să le returnați pentru 3 invocații consecutive folosind codul de mai jos:
@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 }
#Două) Aruncarea excepției prin Mock: Acesta este un scenariu foarte obișnuit când doriți să testați / verificați o aval / dependență care aruncă o excepție și să verificați comportamentul sistemului supus testării. Cu toate acestea, pentru a arunca o excepție de către Mock, va trebui să configurați stub folosind 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 }
Pentru meciuri precum anyInt () și anyString (), nu vă lăsați intimidați, deoarece vor fi acoperite în articolele viitoare. Dar, în esență, acestea vă oferă doar flexibilitatea de a furniza orice valoare Integer și respectiv String, fără niciun argument de funcție specific.
Exemple de cod - Spioni și batjocuri
După cum sa discutat mai sus, atât Spionii, cât și Mock-urile sunt tipul de teste duble și au propriile lor utilizări.
În timp ce spionii sunt utili pentru testarea aplicațiilor vechi (și în cazul în care simulările nu sunt posibile), pentru toate celelalte metode / clase testabile frumos scrise, simulările sunt suficiente pentru majoritatea nevoilor de testare a unității.
Pentru același exemplu: Permiteți-ne să scriem un test folosind Mocks pentru PriceCalculator -> metoda calculatePrice (Metoda calculează prețul articolului mai puțin din reducerile aplicabile)
Clasa PriceCalculator și metoda testată calculatePreț arată așa cum se arată mai jos:
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; } }
Acum să scriem un test pozitiv pentru această metodă.
Vom elimina serviciul utilizatorului și serviciul de articole, după cum se menționează mai jos:
- UserService va returna întotdeauna CustomerProfile cu loyaltyDiscountPercentage setat la 2.
- ItemService va returna întotdeauna un articol cu prețul de bază de 100 și Reducerea aplicabilă de 5.
- Cu valorile de mai sus, prețul așteptat returnat de metoda testată se dovedește a fi de 93 USD.
Iată codul pentru 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); }
După cum puteți vedea, în testul de mai sus - afirmăm că prețul real returnat prin metodă este egal cu prețul așteptat, adică 93,00.
Acum, să scriem un test folosind Spy.
Vom spiona ItemService și vom codifica implementarea ItemService într-un mod în care returnează întotdeauna un articol cu basePrice 200 și aplicabil Reducere de 10,00% (restul configurării simulate rămâne aceeași) ori de câte ori este apelat cu skuCode de 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);
Acum, să vedem un Exemplu a unei excepții aruncată de ItemService întrucât cantitatea de articol disponibilă a fost 0. Vom configura un simulator pentru a arunca o excepție.
@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)); }
Cu exemplele de mai sus, am încercat să explic conceptul de Mocks & Spies și cum pot fi combinate pentru a crea teste unitare eficiente și utile.
Pot exista mai multe combinații ale acestor tehnici pentru a obține o suită de teste care îmbunătățesc acoperirea metodei testate, asigurând astfel un nivel ridicat de încredere în cod și face codul mai rezistent la erorile de regresie.
.net întrebări și răspunsuri la interviu pentru experți
Cod sursa
Interfețe
DiscountCalculator
public interface DiscountCalculator { double calculateDiscount(ItemSku itemSku, double markedPrice); void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile); }
ItemService
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); }
Implementări de interfață
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) { } }
Modele
Profilul cumpărătorului
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; } }
Clasa sub test - 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; } }
Teste unitare - 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)); } }
Diferitele tipuri de chibrituri furnizate de Mockito sunt explicate în viitorul nostru tutorial.
Lectură recomandată
- Diferite tipuri de chibrituri furnizate de Mockito
- Tutorial Mockito: Mockito Framework pentru batjocură în testarea unității
- Crearea testelor de epocă folosind epochs Studio for Eclipse
- Tutorial Python DateTime cu exemple
- Tăiați comanda în Unix cu exemple
- Sintaxa de comandă Cat Unix, Opțiuni cu exemple
- Utilizarea cursorului în MongoDB cu exemple
- Comanda Ls în Unix cu exemple