Mockito - @Spy vs @Mock


102

Mockito - Bir casusun bir nesnedeki gerçek yöntemleri çağırdığını, bir sahte nesnenin yöntemleri çağırdığını anlıyorum. Ayrıca bir kod kokusu olmadıkça casuslardan kaçınılmalıdır. Ancak, casuslar nasıl çalışır ve onları gerçekten ne zaman kullanmalıyım? Taklitlerden nasıl farklılar?


2
olası yinelenen Mockito sahte vs casus
RDS

Yanıtlar:


91

Teknik olarak konuşursak, hem "taklitler" hem de "casuslar" özel bir tür "test çiftleri" dir.

Mockito maalesef bu ayrımı tuhaf yapıyor.

Mockito'daki bir alay , diğer alay çerçevelerindeki normal bir takliddir (çağrıları saplamanıza izin verir; yani, yöntem çağrılarından belirli değerleri döndürür).

Mockito'daki bir casus, diğer alay etme çerçevelerindeki kısmi bir alaydır (nesnenin bir kısmı alay edilecek ve bir kısmı gerçek yöntem çağrılarını kullanacaktır).


43

Her ikisi de yöntem veya alanlarla dalga geçmek için kullanılabilir. Aradaki fark, sahte olarak, casusluk halindeyken tam bir sahte veya sahte nesne yaratıyorsunuz, gerçek nesne var ve sadece onun belirli yöntemlerini casusluk veya saplama yapıyorsunuz.

Casus nesnelerdeyken, tabii ki, gerçek bir yöntem olduğundan, yöntemi saplamadığınız zaman, gerçek yöntem davranışını çağıracaktır. Yöntemi değiştirmek ve alay etmek istiyorsanız, o zaman saplamanız gerekir.

Aşağıdaki örneği bir karşılaştırma olarak düşünün.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

Ne zaman sahte veya casus kullanmalısın? Güvende olmak ve harici hizmetleri aramaktan kaçınmak ve sadece ünitenin içindeki mantığı test etmek istiyorsanız, sahte kullanın. Harici servisi çağırmak ve gerçek bağımlılık çağrısı yapmak istiyorsanız veya basitçe söylemek gerekirse, programı olduğu gibi çalıştırmak ve sadece belirli yöntemleri saplamak, ardından casusluk kullanın. Yani sahte modda casus ve sahte arasındaki fark bu.


Güzel cevap, ancak sadece sahte bir hataya doğrulama () atacak ve listenizi @Before setUp () yönteminde başlatmadığınız sürece, burada olduğu gibi mockList = mock (ArrayList.class); spyList = casus (ArrayList.class); ve burada önerilen sahte ve casus açıklamasını kaldırın. Test ettim ve testlerim şimdi geçiyor.
The_Martian

17

TL; DR versiyonu,

İle mock , sizin için bir çıplak kemik kabuk örneğini oluşturur.

List<String> mockList = Mockito.mock(ArrayList.class);

Casus ile mevcut bir örnek üzerinde kısmen alay edebilirsiniz

List<String> spyList = Mockito.spy(new ArrayList<String>());

Spy için tipik kullanım durumu: Sınıfın parametreleştirilmiş bir kurucusu vardır, önce nesneyi oluşturmak istiyorsunuz.


14

Burada çalıştırılabilir bir örnek oluşturdum https://www.surasint.com/mockito-with-spy/

Bir kısmını buraya kopyalıyorum.

Bu kod gibi bir şeye sahipseniz:

public void transfer( DepositMoneyService depositMoneyService, 
                      WithdrawMoneyService withdrawMoneyService, 
                      double amount, String fromAccount, String toAccount) {
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Sadece DepositMoneyService ve WithdrawMoneyService ile dalga geçebileceğiniz için casusa ihtiyacınız olmayabilir.

Ancak bazı eski kodlarda, bağımlılık kodun içindedir:

    public void transfer(String fromAccount, String toAccount, double amount) {
        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();
        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Evet, ilk koda geçebilirsiniz ancak daha sonra API değiştirilir. Bu yöntem birçok yerde kullanılıyorsa, hepsini değiştirmelisiniz.

Alternatif, bağımlılığı şu şekilde çıkarabilmenizdir:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();
        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Daha sonra casusu kullanarak bağımlılığı şu şekilde enjekte edebilirsiniz:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target)
            .proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target)
            .proxyWithdrawMoneyServiceCreator();

Yukarıdaki bağlantıda daha fazla ayrıntı.


13

Başlamak için en iyi yer muhtemelen mockito için dokümanlardır .

Genel bir not olarak, sahte mock, taslak oluşturmanıza izin verir.

Örneğin, bu yöntem pahalı bir işlem yapıyorsa, bir saplama yöntemi oluşturursunuz. Diyelim ki bir veritabanı bağlantısı alıyor, veritabanından bir değer alıyor ve arayana geri veriyor. Db bağlantısının kurulması 30 saniye sürebilir ve bu da test yürütme sürecinizi muhtemelen bağlam değiştirme (veya testi çalıştırmayı durdurma) noktasına kadar yavaşlatır.

Test ettiğiniz mantık veritabanı bağlantısını umursamıyorsa, bu yöntemi sabit kodlanmış bir değer döndüren bir saplama ile değiştirebilirsiniz.

Sahte casus, bir yöntemin diğer yöntemleri çağırıp çağırmadığını kontrol etmenizi sağlar. Bu, eski kodu teste tabi tutmaya çalışırken çok yararlı olabilir.

Yan etkilerle çalışan bir yöntemi test ediyorsanız, o zaman sahte bir casus kullanırsınız. Bu, gerçek nesneye çağrıları delege eder ve yöntem çağrısını, çağrılma sayısını vb.


7

Kısacası:

@Spyve @Mockkodun test edilmesinde yoğun bir şekilde kullanılır, ancak geliştiriciler bunlardan birini ne zaman kullanacakları konusunda kafa karıştırırlar ve bu nedenle geliştiriciler @Mockgüvenli olmak için kullanmaya başlarlar.

  • Bu yöntemi gerçekten çağırmadan @Mockyalnızca işlevselliği harici olarak test etmek istediğinizde kullanın .
  • Çok çağrılan yöntemle @Spyişlevselliği harici olarak + dahili olarak test etmek istediğinizde kullanın .

Aşağıda Amerika'da Election20xx senaryosunu aldığım örnek var .

Verenler göre ayrılabilir VotersOfBelow21ve VotersOfABove21.

İdeal Çık anket çünkü Trump seçimleri kazanacağını söylüyor VotersOfBelow21ve VotersOfABove21her iki koz söyleyerek oy verecektir " Başkan Trump seçildi "

Ancak bu gerçek senaryo değil:

Her iki yaş grubundan seçmenler de Trump'a oy verdi çünkü Trump'tan başka etkili seçenekleri yoktu.

Peki nasıl test edersiniz?

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

Şimdi, yukarıdaki ilk iki sınıfa dikkat edin, her iki yaş grubu da kozdan daha iyi bir seçeneğe sahip olmadıklarını söylüyor. Bu açıkça, sırf başka seçenekleri olmadığı için Trump'a oy verdikleri anlamına geliyor.

Şimdi, ElectionOfYear20XX Trump'ın her iki yaş grubunun da ezici bir çoğunlukla ona oy vermesi nedeniyle kazandığını söylüyor.

ElectionOfYear20XX@Mock ile Test edersek, Trump'ın kazanmasının gerçek sebebini bulamayabiliriz, sadece dış sebebi test etmiş oluruz.

ElectionOfYear20XX@ Spy ile test edersek, o zaman Trump'ın neden harici çıkış anket sonuçlarıyla, yani dahili + harici olarak kazanmasının gerçek sebebini anlarız.


Bizim ELectionOfYear20XX_Testsınıfı:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Bu, yalnızca mantık testi sonuçlarının çıktısını almalıdır , yani harici kontrol:

We elected President Trump 
We elected President Trump 

@SpyGerçek yöntem çağrısı ile harici ve dahili olarak test etme .

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Çıktı:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 

6

Bu tavsiyenin basitliğini beğendim:

  • Güvende olmak ve harici hizmetleri aramaktan kaçınmak ve sadece ünitenin içindeki mantığı test etmek istiyorsanız, sahte kullanın .
  • Harici servisi çağırmak ve gerçek bağımlılıkları aramak istiyorsanız veya basitçe söylemek gerekirse, programı olduğu gibi çalıştırmak ve sadece belirli yöntemleri saplamak, ardından casusluk kullanın .

Kaynak: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Ortak bir fark şudur:

  • Bir bağımlılığın yöntemlerini doğrudan saplamak istiyorsanız, bu bağımlılığı alay edin.
  • Eğer bir bağımlılık verileri saplama istiyorsanız tüm yöntemler o zaman, gereken değerleri test dönüş böylece Spy bağımlılık olduğunu.

Spy ve Mock'un her zaman bağımlılıklar için olduğunu ve test edilen sistem için olmadığını unutmayın.
leo9r
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.