creating mocks spies mockito with code examples
Mockito Spy ve Mocks Eğitimi:
Bunda Mockito Eğitimi serisi , önceki eğitimimiz bize bir Mockito Framework'e Giriş . Bu eğitimde Mockito'daki Mocks ve Spies kavramını öğreneceğiz.
Sahte ve Casuslar nedir?
Hem Sahte hem de Casuslar, birim testleri yazarken yardımcı olan test çiftleri türleridir.
Taklitler, bağımlılığın tam yerine geçer ve model üzerinde bir yöntem her çağrıldığında belirtilen çıktıyı döndürecek şekilde programlanabilir. Mockito, bir taklidin tüm yöntemleri için varsayılan bir uygulama sağlar.
Ne öğreneceksin:
- Casus nedir?
- Mocks Oluşturma
- Casuslar Yaratmak
- Test Kapsamındaki Sınıf / Nesne için Sahte Bağımlılıklar Nasıl Enjekte Edilir?
- İpuçları & Hileler
- Kod Örnekleri - Casuslar ve Taklitler
- Kaynak kodu
- Önerilen Kaynaklar
Casus nedir?
Casuslar, aslında alay konusu bağımlılığın gerçek bir örneğini anlatan bir sarmalayıcıdır. Bunun anlamı, yeni bir Nesne veya bağımlılık örneği gerektirmesi ve ardından alay konusu nesnenin üzerine bir sarmalayıcı eklemesidir. Varsayılan olarak, Casuslar stub olmadıkça Nesnenin gerçek yöntemlerini çağırır.
Casuslar, yöntem çağrısına hangi argümanların verildiği, gerçek yöntemin çağrıldığı gibi bazı ek güçler sağlar.
Özetle, Spies için:
- Nesnenin gerçek örneği gereklidir.
- Casuslar, casusluk yapılan nesnenin bazı (veya tüm) yöntemlerini saplama esnekliği sağlar. O sırada, casus esasen kısmen alay edilen veya saplanmış bir nesne olarak adlandırılır veya ona atıfta bulunur.
- Casus nesnede çağrılan etkileşimler, doğrulama için izlenebilir.
Genel olarak, Casuslar çok sık kullanılmaz, ancak bağımlılıkların tamamen alay edilemediği eski uygulamaların birim testi için yardımcı olabilir.
Tüm Mock and Spy açıklamaları için, alay etmek / casusluk yapmak istediğimiz 'DiscountCalculator' adlı hayali bir sınıfa / nesneye atıfta bulunuyoruz.
Aşağıda gösterildiği gibi bazı yöntemleri vardır:
calculateDiscount - Belirli bir ürünün indirimli fiyatını hesaplar.
getDiscountLimit - Ürün için üst limit indirim limitini getirir.
Mocks Oluşturma
# 1) Kod ile sahte oluşturma
Mockito, Mockito'nun birkaç aşırı yüklenmiş versiyonunu verir. Mocks yöntemi ve bağımlılıklar için alay oluşturmaya izin verir.
Sözdizimi:
Mockito.mock(Class classToMock)
Misal:
Kodda bir model oluşturmak için sınıf adının DiscountCalculator olduğunu varsayalım:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Mock'un hem arayüz hem de somut bir sınıf için oluşturulabileceğini unutmamak önemlidir.
Bir nesneyle alay edildiğinde, stub yapılmadıkça tüm yöntemler varsayılan olarak boş döndürür .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Ek Açıklamalar ile sahte oluşturma
Mockito kitaplığının statik 'sahte' yöntemini kullanarak alay etmek yerine, '@Mock' ek açıklamasını kullanarak taklitler oluşturmanın kısa bir yolunu da sunar.
Bu yaklaşımın en büyük avantajı, basit olması ve bildirimi ve esasen başlatmayı birleştirmeye izin vermesidir. Aynı zamanda, testleri daha okunaklı hale getirir ve aynı model birkaç yerde kullanıldığında taklitlerin tekrar tekrar başlatılmasını önler.
Bu yaklaşımla Mock başlatmayı sağlamak için, test edilen sınıf için 'MockitoAnnotations.initMocks (this)' olarak adlandırmamız gerekir. Bu, Junit'in 'beforeEach' yönteminin bir parçası olmak için ideal adaydır ve bu, o sınıftan bir test her yürütüldüğünde taklitlerin başlatılmasını sağlar.
Sözdizimi:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Casuslar Yaratmak
Mocks'a benzer şekilde, Casuslar da 2 şekilde oluşturulabilir:
# 1) Kod ile casus oluşturma
Mockito.spy, gerçek nesne örneğinin etrafında bir 'casus' nesnesi / sarmalayıcı oluşturmak için kullanılan statik yöntemdir.
Sözdizimi:
b ağacı ve b + ağacı
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Ek Açıklamalarla casus oluşturma
Mock'a benzer şekilde, Casuslar @ Spy ek açıklaması kullanılarak oluşturulabilir.
Casus başlatma için, casusun başlatılmasını sağlamak için, Casus gerçek testte kullanılmadan önce MockitoAnnotations.initMocks'un (bu) çağrıldığından emin olmalısınız.
Sözdizimi:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Test Kapsamındaki Sınıf / Nesne için Sahte Bağımlılıklar Nasıl Enjekte Edilir?
Diğer alay edilen bağımlılıklar ile test edilen sınıfın sahte bir nesnesini oluşturmak istediğimizde, @InjectMocks açıklamasını kullanabiliriz.
Bunun esas olarak yaptığı şey, @Mock (veya @Spy) ek açıklamaları ile işaretlenen tüm nesnelerin, Nesne sınıfına Yüklenici veya özellik enjeksiyonu olarak enjekte edilmesi ve ardından etkileşimlerin son Alaylı nesne üzerinde doğrulanabilmesidir.
Tekrar belirtmeye gerek yok, @InjectMocks, sınıfın yeni bir Nesnesini oluşturmaya karşı bir kısaltmadır ve bağımlılıkların alay edilmiş nesnelerini sağlar.
Bunu bir Örnek ile anlayalım:
Bağımlılıklar olarak DiscountCalculator ve UserService'e sahip olan ve Constructor veya Property alanları aracılığıyla enjekte edilen bir PriceCalculator sınıfı olduğunu varsayalım.
Dolayısıyla, Fiyat hesaplayıcı sınıfı için Mocked uygulamasını oluşturmak için 2 yaklaşım kullanabiliriz:
# 1) Oluşturun PriceCalculator'ın yeni bir örneği ve Mocked bağımlılıkları enjekte edin
@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) Oluşturun PriceCalculator'ın alay edilmiş bir örneği ve @InjectMocks ek açıklaması aracılığıyla bağımlılıkları enjekte edin
@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 ek açıklaması aslında aşağıdaki yaklaşımlardan birini kullanarak alay konusu bağımlılıkları enjekte etmeye çalışır:
- Yapıcı Bazlı Enjeksiyon - Test edilen sınıf için Constructor'ı kullanır.
- Ayarlayıcı Yöntemlere Dayalı - Bir Oluşturucu orada olmadığında, Mockito özellik belirleyicileri kullanarak enjekte etmeye çalışır.
- Alan Tabanlı - Yukarıdaki 2 mevcut olmadığında, doğrudan alanlar aracılığıyla enjekte etmeye çalışır.
İpuçları & Hileler
# 1) Aynı yöntemin farklı çağrıları için farklı koçanların kurulması:
Test edilen yöntem içinde stubbed yöntem birden çok kez çağrıldığında (veya stubbed yöntem döngü içindeyse ve her seferinde farklı çıktı döndürmek istiyorsanız), Mock'u her seferinde farklı stubbed yanıtlar döndürecek şekilde ayarlayabilirsiniz.
Örneğin: Varsayalım ki istiyorsun ItemService Ardışık 3 çağrı için farklı bir öğe iade etmek ve testler altında yönteminizde Öğe1, Öğe2 ve Öğe3 olarak beyan edilen Öğeleriniz varsa, aşağıdaki kodu kullanarak bunları 3 ardışık çağrı için basitçe iade edebilirsiniz:
@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 }
#iki) Mock ile İstisna Oluşturma: Bu, bir istisna atan bir aşağı akış / bağımlılığı test etmek / doğrulamak ve test edilen sistemin davranışını kontrol etmek istediğinizde çok yaygın bir senaryodur. Bununla birlikte, Mock tarafından bir istisna atmak için, thenThrow'u kullanarak saplama kurmanız gerekir.
@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 () ve anyString () gibi eşleşmeler için, gelecek makalelerde ele alınacakları için gözünüzü korkutmayın. Ancak özünde, size herhangi bir Tamsayı ve Dize değerini sırasıyla herhangi bir özel işlev argümanı olmadan sağlama esnekliği sağlarlar.
Kod Örnekleri - Casuslar ve Taklitler
Daha önce tartışıldığı gibi, hem Spies hem de Mocks, test çiftlerinin türüdür ve kendi kullanımlarına sahiptir.
Casuslar eski uygulamaları test etmek için yararlı olsa da (ve taklitlerin mümkün olmadığı durumlarda), güzel yazılmış diğer tüm test edilebilir yöntemler / sınıflar için Mocks, Birim test ihtiyaçlarının çoğunu karşılamaktadır.
Aynı Örnek için: PriceCalculator için Mocks kullanarak bir test yazalım -> calculatePrice yöntemi (Yöntem, uygulanabilir indirimlerden itemPrice eksiğini hesaplar)
PriceCalculator sınıfı ve test calculatePrice altındaki yöntem, aşağıda gösterildiği gibi görünür:
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; } }
Şimdi bu yöntem için pozitif bir test yazalım.
UserService ve item service'i aşağıda belirtildiği gibi stub edeceğiz:
- UserService, CustomerProfile'ı her zaman loyaltyDiscountPercentage değeri 2 olarak ayarlanmış şekilde döndürür.
- ItemService her zaman basePrice değeri 100 ve uygulanabilirDiscount 5 olan bir Öğe döndürür.
- Yukarıdaki değerlerle, test edilen yöntemin döndürdüğü beklenenPrice 93 $ olur.
İşte test için kod:
@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); }
Gördüğünüz gibi, yukarıdaki testte - Yöntem tarafından döndürülen realPrice değerinin, beklenenPrice yani 93,00 olduğunu iddia ediyoruz.
Şimdi, Spy kullanarak bir test yazalım.
ItemService'i gözetleyeceğiz ve ItemService uygulamasını her zaman basePrice 200 ve% 10.00 uygulanabilirDiscount değerine sahip bir öğe döndürecek şekilde kodlayacağız (sahte kurulumun geri kalanı aynı kalır) 2367 skuCode ile her çağrıldığında.
youtube mp3 dönüştürücü uygulaması ücretsiz indirme
@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);
Şimdi bir bakalım Misal mevcut Öğe miktarı 0 olduğu için ItemService tarafından atılan bir istisna. Bir istisna atmak için sahte ayar yapacağız.
@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)); }
Yukarıdaki örneklerle, Taklitler ve Casuslar kavramını ve bunların etkili ve yararlı Birim testleri oluşturmak için nasıl birleştirilebileceğini açıklamaya çalıştım.
Test edilen yöntemin kapsamını artıran, böylece koda büyük bir güven düzeyi sağlayan ve kodu regresyon hatalarına karşı daha dirençli hale getiren bir dizi test elde etmek için bu tekniklerin birden fazla kombinasyonu olabilir.
Kaynak kodu
Arayüzler
İndirim Hesaplayıcısı
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); }
Arayüz Uygulamaları
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) { } }
Modeller
Müşteri profili
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; } }
Test Altındaki Sınıf - Fiyat Hesaplayıcı
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; } }
Birim Testleri - Fiyat Hesaplayıcı Birim Testleri
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 tarafından sağlanan Farklı Eşleştirici Türleri yaklaşan eğitimimizde açıklanmaktadır.
PREV Eğitimi | SONRAKİ Eğitici
Önerilen Kaynaklar
- Mockito Tarafından Sağlanan Farklı Eşleştirici Türleri
- Mockito Eğitimi: Birim Testinde Alay için Mockito Çerçevesi
- Epochs Studio for Eclipse kullanarak çağ testleri oluşturma
- Örneklerle Python DateTime Eğitimi
- Örneklerle Unix'te Kesme Komutu
- Unix Cat Komut Sözdizimi, Örneklerle Seçenekler
- MongoDB'de İmleç Kullanımı Örneklerle
- Örneklerle Unix'te Ls Komutu