簡體   English   中英

使用 Mockito 時模擬和間諜有什么區別?

[英]What is the difference between mocking and spying when using Mockito?

使用 Mockito 間諜的用例是什么?

在我看來,每個間諜用例都可以使用 callRealMethod 進行模擬處理。

我可以看到的一個區別是,如果您希望大多數方法調用都是真實的,它會節省一些代碼行來使用模擬與間諜。 是這樣還是我錯過了更大的圖景?

答案在文檔中

真正的部分模擬(自 1.8.0 起)

最后,經過郵件列表上的多次內部辯論和討論,Mockito 添加了部分模擬支持。 以前我們將部分模擬視為代碼異味。 但是,我們發現了部分模擬的合法用例。

在 1.8 版之前 spy() 並沒有產生真正的部分模擬,這讓一些用戶感到困惑。 閱讀有關間諜的更多信息: 此處或在 spy(Object) 方法的 javadoc 中。

callRealMethod()是在spy()之后引入的,但 spy() 當然留在那里,以確保向后兼容性。

否則,你是對的:間諜的所有方法都是真實的,除非被存根。 除非callRealMethod()否則模擬的所有方法都將被存根。 一般來說,我更喜歡使用callRealMethod() ,因為它不會強迫我使用doXxx().when()習語而不是傳統的when().thenXxx()

間諜和模擬之間的區別

當 Mockito 創建一個模擬時——它是從類型的類中創建的,而不是從實際實例中創建的。 模擬只是創建了一個類的基本外殼實例,完全用於跟蹤與它的交互。 另一方面,間諜將包裝現有的實例。 它的行為方式仍然與普通實例相同——唯一的區別是它也將被檢測以跟蹤與它的所有交互。

在下面的例子中——我們創建了一個 ArrayList 類的模擬:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

正如你所看到的——向模擬列表中添加一個元素實際上並沒有添加任何東西——它只是調用了沒有其他副作用的方法。 另一方面,spy 的行為會有所不同——它實際上會調用 add 方法的真正實現並將元素添加到底層列表中:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

在這里我們可以肯定地說調用了對象的真正內部方法,因為當你調用 size() 方法時,你得到的 size 為 1,但這個 size() 方法沒有被模擬! 那么1從哪里來? 內部真實的 size() 方法被調用,因為 size() 沒有被模擬(或存根),因此我們可以說條目被添加到真實對象中。

來源: http : //www.baeldung.com/mockito-spy + 自我筆記。

如果有一個具有 8 個方法的對象,並且您有一個測試要調用 7 個真正的方法並存根一個方法,那么您有兩個選擇:

  1. 使用模擬,您必須通過調用 7 callRealMethod 並存根一個方法來設置它
  2. 使用spy你必須通過一種方法來設置它

doCallRealMethod官方文檔建議使用 spy 進行部分doCallRealMethod

另請參閱 javadoc spy(Object) 以了解有關部分模擬的更多信息。 Mockito.spy() 是創建部分模擬的推薦方法。 原因是它保證針對正確構造的對象調用真正的方法,因為您負責構造傳遞給 spy() 方法的對象。

當您想為遺留代碼創建單元測試時,Spy 會很有用。

我在這里創建了一個可運行的例子https://www.surasint.com/mockito-with-spy/ ,我在這里復制了一些。

如果你有這樣的代碼:

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

您可能不需要 spy,因為您可以模擬 DepositMoneyService 和 WithdrawMoneyService。

但是對於一些遺留代碼,依賴在代碼中是這樣的:

    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);
    }

是的,您可以更改為第一個代碼,但隨后更改了 API。 如果這個方法被很多地方使用,你必須改變所有的地方。

另一種方法是您可以像這樣提取依賴項:

    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();
    }

然后您可以使用 spy 注入依賴項,如下所示:

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();

在上面的鏈接中有更多詳細信息。

[測試雙重類型]

Mock vs Spy

Mock是一個裸雙對象。 此對象具有相同的方法簽名但實現為空並返回默認值 - 0 和 null

Spy是一個克隆的雙重對象。 新對象是基於真實對象克隆的,但您可以模擬它

class A {
    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() { foo4(); }

    void foo4() { }
}
@Test
public void testMockA() {
    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM