creating mocks spies mockito with code examples
Підручник з Mockito Spy and Mocks:
У цьому Серія підручників Mockito , наш попередній підручник дав нам Вступ до Mockito Framework . У цьому підручнику ми вивчимо поняття знущань і шпигунів у Mockito.
Що таке насмішки та шпигуни?
І “Мокети”, і “Шпигуни” - це тістові дублі, які корисні для написання модульних тестів.
Макети є повною заміною залежності і можуть бути запрограмовані на повернення вказаного виводу при кожному виклику методу в макеті. Mockito забезпечує реалізацію за замовчуванням для всіх методів макету.
Що ви дізнаєтесь:
- Що таке шпигуни?
- Створення знущань
- Створення шпигунів
- Як вводити знущані залежності для тестуваного класу / об’єкта?
- Поради та підказки
- Приклади коду - Шпигуни та насмішки
- Вихідний код
- Рекомендована література
Що таке шпигуни?
Шпигуни, по суті, є обгорткою реального екземпляру знущаної залежності. Це означає, що він вимагає нового екземпляра Об'єкта або залежності, а потім додає над ним обгортку знущаного об'єкта. За замовчуванням Шпигуни викликають реальні методи Об'єкта, якщо вони не заглушені.
Шпигуни дійсно надають певні додаткові повноваження, такі як аргументи, що надходять до виклику методу, чи справді викликаний метод взагалі тощо.
У двох словах, для шпигунів:
- Потрібний реальний екземпляр об’єкта.
- Шпигуни надають гнучкість, щоб заглушити деякі (або всі) методи шпигунського об'єкта. У той час шпигуна, по суті, називають або посилають на частково знущаний або нечіткий предмет.
- Взаємодії, що викликаються на об'єкті, що підглядається, можна відстежувати для перевірки.
Загалом, шпигуни не дуже часто використовуються, але можуть бути корисними для модульного тестування застарілих додатків, де залежності неможливо повністю висміяти.
У всьому описі Макет і Шпигун ми маємо на увазі вигаданий клас / об’єкт під назвою „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)’ для тестованого класу. Це ідеальний кандидат для участі в методі Junit 'beforeEach', який гарантує, що макети ініціалізуються кожного разу, коли тест виконується з цього класу.
Синтаксис:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Створення шпигунів
Подібно до Знущань, Шпигунів також можна створювати двома способами:
# 1) Створення шпигуна за допомогою коду
Mockito.spy - це статичний метод, який використовується для створення 'шпигунського' об'єкта / обгортки навколо реального екземпляра об'єкта.
Синтаксис:
безкоштовне програмне забезпечення для видалення шкідливих програм
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 як залежності, які вводяться через поля конструктора або властивості.
Отже, для того, щоб створити реалізацію Mocked для класу калькулятора цін, ми можемо використовувати 2 підходи:
# 1) Створити новий екземпляр PriceCalculator та ін’єкційні знущані залежності
@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, вам потрібно буде налаштувати заглушку за допомогою 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 (), не лякайтесь, оскільки вони будуть розглянуті в майбутніх статтях. Але по суті, вони просто дають вам гнучкість для надання будь-якого цілого і рядкового значення відповідно без будь-яких конкретних аргументів функції.
Приклади коду - Шпигуни та насмішки
Як вже обговорювалося раніше, і шпигуни, і насмішки є типом тестових парних і мають свої звички.
Хоча шпигуни корисні для тестування застарілих додатків (а там, коли знущання неможливі), для всіх інших добре написаних перевірених методів / класів, Макети вистачає більшості потреб модульного тестування.
Для того ж прикладу: Давайте напишемо тест, використовуючи Mocks for PriceCalculator -> methodPrice method (Метод обчислює itemPrice менший за відповідні знижки)
Клас 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; } }
Тепер напишемо позитивний тест для цього методу.
Ми збираємося заглушити 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); }
Як бачите, у наведеному вище тесті - ми стверджуємо, що фактична ціна, повернута методом, дорівнює очікуваній ціні, тобто 93,00.
Тепер давайте напишемо тест за допомогою Spy.
Ми шпигуватимемо 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)); }
На наведених вище прикладах я спробував пояснити концепцію знущань та шпигунів та способи їх поєднання для створення ефективних та корисних модульних тестів.
Для отримання набору тестів може бути кілька комбінацій цих методів, що покращують охоплення тестованого методу, забезпечуючи тим самим великий рівень довіри до коду та роблячи код більш стійким до помилок регресії.
найкраще безкоштовне програмне забезпечення для завантаження відео YouTube
Вихідний код
Інтерфейси
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; } }
Тестовий клас - Калькулятор цін
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: Mockito Framework для знущань під час модульного тестування
- Створення тестів епох за допомогою epochs Studio для Eclipse
- Підручник із прикладами Python DateTime
- Вирізати команду в Unix з прикладами
- Синтаксис команд Unix Cat, варіанти з прикладами
- Використання курсору в MongoDB з прикладами
- Команда Ls в Unix з прикладами