creating mocks spies mockito with code examples
Урок за Mockito Spy and Mocks:
В това Mockito Tutorial серия , предишният ни урок ни даде Въведение в Mockito Framework . В този урок ще научим концепцията за подигравки и шпиони в Mockito.
Какво са подигравки и шпиони?
И Mocks, и Spies са видовете двойни тестове, които са полезни при писането на единични тестове.
Макетите са пълна заместител на зависимостта и могат да бъдат програмирани да връщат посочения изход, когато се извика метод в макета. Mockito осигурява изпълнение по подразбиране за всички методи на макет.
Какво ще научите:
- Какво представляват шпионите?
- Създаване на подигравки
- Създаване на шпиони
- Как да инжектирам подигравани зависимости за тествания клас / обект?
- Съвети и трикове
- Примери за кодове - шпиони и подигравки
- Програмен код
- Препоръчително четене
Какво представляват шпионите?
По същество шпионите са обвивка на реален екземпляр на подиграваната зависимост. Това означава, че той изисква нов екземпляр на Обекта или зависимостта и след това добавя обвивка на подигравания обект върху него. По подразбиране шпионите извикват реални методи на обекта, освен ако не са заглушени.
Шпионите предоставят определени допълнителни правомощия като какви аргументи са били предоставени на извикването на метода, дали изобщо е извикан истинският метод и т.н.
Накратко, за шпиони:
- Изисква се реалният екземпляр на обекта.
- Шпионите дават гъвкавост за заглушаване на някои (или всички) методи на шпионирания обект. По това време шпионинът по същество бива извикан или насочен към частично подиграван или погълнат обект.
- Взаимодействията, извикани върху шпиониран обект, могат да бъдат проследени за проверка.
Като цяло шпионите не се използват много често, но могат да бъдат полезни за тестване на наследени приложения, където зависимостите не могат да бъдат напълно подигравани.
За цялото описание на Mock and Spy ние се позоваваме на фиктивен клас / обект, наречен ‘DiscountCalculator’, който искаме да подиграваме / шпионираме.
Той има някои методи, както е показано по-долу:
изчисли отстъпка - Изчислява намалената цена на даден продукт.
getDiscountLimit - Извлича горната граница на отстъпка за продукта.
Създаване на подигравки
# 1) Подигравателно създаване с код
Mockito дава няколко претоварени версии на Mockito. Mocks метод и позволява създаване на макети за зависимости.
Синтаксис:
Mockito.mock(Class classToMock)
Пример:
Да предположим, че името на класа е DiscountCalculator, за да създадете фалшив код:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Важно е да се отбележи, че Mock може да бъде създаден както за интерфейс, така и за конкретен клас.
Когато даден обект се подиграва, освен ако не се забият всички методи, връщат null по подразбиране .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Помислено създаване с пояснения
Вместо да се подигравате, използвайки статичен метод 'mock' на библиотеката Mockito, той също така предоставя стенографичен начин за създаване на макети, използвайки анотация '@Mock'
Най-голямото предимство на този подход е, че е прост и позволява да се комбинират декларация и по същество инициализация. Той също така прави тестовете по-четливи и избягва многократната инициализация на макети, когато същият макет се използва на няколко места.
За да се осигури Mock инициализация чрез този подход, е необходимо да извикаме ‘MockitoAnnotations.initMocks (this)’ за тествания клас. Това е идеалният кандидат да бъде част от метода ‘beforeEach’ на Junit, който гарантира, че макетите се инициализират всеки път, когато се изпълнява тест от този клас.
Синтаксис:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Създаване на шпиони
Подобно на Mocks, шпионите също могат да бъдат създадени по 2 начина:
# 1) Създаване на шпиони с код
Mockito.spy е статичният метод, който се използва за създаване на ‘шпионски’ обект / обвивка около реалния екземпляр на обект.
Синтаксис:
най-добрият софтуер за конвертор на видео за Windows
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Създаване на шпиони с Анотации
Подобно на Mock, шпионите могат да бъдат създадени с помощта на @Spy анотация.
За инициализацията на шпиони също трябва да се уверите, че MockitoAnnotations.initMocks (това) са извикани преди шпионинът да бъде използван в действителния тест, за да се инициализира шпионинът.
Синтаксис:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Как да инжектирам подигравани зависимости за тествания клас / обект?
Когато искаме да създадем фалшив обект на тествания клас с останалите измислени зависимости, можем да използваме анотация @InjectMocks.
Това, което по същество прави, е, че всички обекти, отбелязани с @Mock (или @Spy) анотации, се инжектират като изпълнител или инжекция на свойство в класа Object и след това взаимодействията могат да бъдат проверени на крайния Mocked обект.
Отново, излишно е да споменавам, @InjectMocks е стенография срещу създаването на нов Обект на класа и предоставя подигравани обекти на зависимостите.
Нека разберем това с пример:
Да предположим, че има клас PriceCalculator, който има DiscountCalculator и UserService като зависимости, които се инжектират чрез полетата Constructor или Property.
Така че, за да създадем Mocked изпълнение за клас калкулатор на цена, можем да използваме 2 подхода:
# 1) Създаване нов екземпляр на PriceCalculator и инжектирайте 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) Създаване подиграван екземпляр на PriceCalculator и инжектиране на зависимости чрез анотация @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);
Анотацията на InjectMocks всъщност се опитва да инжектира подигравани зависимости, използвайки един от подходите по-долу:
- Инжектиране въз основа на конструктор - Използва конструктор за тествания клас.
- Въз основа на методи за определяне - Когато конструктор не е там, Mockito се опитва да инжектира с помощта на задатели на свойства.
- Полево базиран - Когато горните 2 не са налични, той директно се опитва да инжектира чрез полета.
Съвети и трикове
# 1) Настройване на различни заглушки за различни повиквания от един и същ метод:
Когато stubbed метод се извиква няколко пъти в тествания метод (или stubbed методът е в цикъла и искате да връщате различен изход всеки път), тогава можете да настроите Mock да връща различен stubbed отговор всеки път.
Например: Да предположим, че искате ItemService за да върнете различен елемент за 3 последователни повиквания и имате елементи, декларирани във вашия метод при тестове като Item1, Item2 и Item3, след което можете просто да ги върнете за 3 последователни извиквания, използвайки кода по-долу:
@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 }
# две) Изхвърляне на изключение чрез Mock: Това е много често срещан сценарий, когато искате да тествате / проверите низходяща / зависимост, хвърляща изключение и да проверите поведението на тестваната система. Въпреки това, за да хвърлите изключение от Mock, ще трябва да настроите мъниче с помощта на 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 }
За мачове като anyInt () и anyString (), не се плашете, тъй като те ще бъдат обхванати в предстоящите статии. Но по същество те просто ви дават гъвкавостта да предоставите всяка стойност Integer и String, съответно, без конкретни аргументи на функцията.
Примери за кодове - шпиони и подигравки
Както беше обсъдено по-рано, и шпионите, и макетите са от типа тестови двойки и имат свои собствени обичаи.
Докато шпионите са полезни за тестване на наследени приложения (и където подигравките не са възможни), за всички останали добре написани тестваеми методи / класове, Mocks са достатъчни за повечето от нуждите на Unit тестване.
За същия пример: Нека напишем тест с помощта на Mocks for PriceCalculator -> methodPrice method (Методът изчислява itemPrice по-малко от приложимите отстъпки)
Класът PriceCalculator и тестваният метод изчисляватPrice изглежда, както е показано по-долу:
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; } }
Сега нека напишем положителен тест за този метод.
Ще смажем userService и услугата за артикули, както е споменато по-долу:
- UserService винаги ще връща CustomerProfile с loyaltyDiscountPercentage, зададен на 2.
- ItemService винаги ще върне артикул с основната цена 100 и приложимата отстъпка 5.
- С горните стойности, очакваната цена, върната от тествания метод, се оказва 93 $.
Ето кода за тест:
@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); }
Както можете да видите, в горния тест - ние твърдим, че действителнатаPrice, върната от метода, е равна на очакванатаPrice, т.е. 93.00.
Сега, нека напишем тест, използвайки Spy.
отваряне на eps файл в windows
Ще шпионираме ItemService и ще кодираме реализацията ItemService по начин, който винаги връща елемент с basePrice 200 и приложимата отстъпка от 10,00% (останалата част от макетната настройка остава същата), когато се извика с 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);
Сега да видим Пример на изключение, хвърлено от ItemService, тъй като наличното количество на артикула е 0. Ще настроим макет, за да хвърлим изключение.
@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)); }
С горните примери се опитах да обясня концепцията за Mocks & Spies и как те могат да бъдат комбинирани, за да създадат ефективни и полезни Unit тестове.
Може да има множество комбинации от тези техники, за да получите набор от тестове, които подобряват покритието на тествания метод, като по този начин осигуряват голямо ниво на доверие в кода и правят кода по-устойчив на грешки в регресията.
Програмен код
Интерфейси
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); }
Изпълнения на интерфейса
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) { } }
Модели
Клиентски профил
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; } }
Тестван клас - 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; } }
Единични тестове - 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)); } }
Различните видове съвпадения, предоставени от Mockito, са обяснени в нашия предстоящ урок.
Препоръчително четене
- Различни видове съвпадения, предоставени от Mockito
- Mockito Tutorial: Mockito Framework for Mocking in Unit Testing
- Създаване на тестове за епохи с помощта на epochs Studio за Eclipse
- Урок за Python DateTime с примери
- Изрежете командата в Unix с примери
- Синтаксис на командата Unix Cat, Опции с примери
- Използване на курсора в MongoDB с примери
- Ls Command в Unix с примери