Mockito - @Spy vs @Mock


99

Mockito - Tôi hiểu một gián điệp gọi các phương thức thực trên một đối tượng, trong khi một kẻ giả gọi các phương thức trên đối tượng kép. Ngoài ra cần phải tránh gián điệp trừ khi có mùi mã. Tuy nhiên, gián điệp hoạt động như thế nào và khi nào tôi thực sự nên sử dụng chúng? Chúng khác gì với những trò giễu cợt?


2
có thể trùng lặp của Mockito giả vs gián điệp
RDS

Câu trả lời:


88

Về mặt kỹ thuật, cả "mocks" và "spy" đều là một loại "test double" đặc biệt.

Mockito thật không may làm cho sự khác biệt trở nên kỳ lạ.

Một mô hình trong mockito là một mô phỏng bình thường trong các khung chế tạo khác (cho phép bạn khai thác các lời gọi; nghĩa là, trả về các giá trị cụ thể từ các lệnh gọi phương thức).

Một gián điệp trong mockito là một mô phỏng một phần trong các khuôn khổ chế nhạo khác (một phần của đối tượng sẽ bị chế nhạo và một phần sẽ sử dụng các lệnh gọi phương thức thực).


41

Cả hai đều có thể được sử dụng để mô phỏng các phương thức hoặc trường. Sự khác biệt là trong giả lập, bạn đang tạo ra một mô hình hoặc đối tượng giả hoàn chỉnh trong khi ở trong spy, có đối tượng thực và bạn chỉ gián điệp hoặc khai thác các phương pháp cụ thể của nó.

Tất nhiên, khi ở trong các đối tượng gián điệp, vì nó là một phương thức thực nên khi bạn không khai báo phương thức đó, thì nó sẽ gọi hành vi của phương thức thực. Nếu bạn muốn thay đổi và bắt chước phương pháp, thì bạn cần phải khai báo nó.

Hãy coi ví dụ dưới đây như một phép so sánh.

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

Khi shoud bạn sử dụng mô hình hoặc gián điệp? Nếu bạn muốn an toàn và tránh gọi các dịch vụ bên ngoài và chỉ muốn kiểm tra tính logic bên trong của thiết bị, thì hãy sử dụng mô hình. Nếu bạn muốn gọi dịch vụ bên ngoài và thực hiện việc gọi phụ thuộc thực sự, hay nói đơn giản, bạn muốn chạy chương trình như nó vốn có và chỉ cần khai báo các phương thức cụ thể, thì hãy sử dụng spy. Vì vậy, đó là sự khác biệt giữa gián điệp và giả trong mockito.


Câu trả lời tốt nhưng nó sẽ ném verify () trên một lỗi chỉ mô phỏng và sẽ không chạy các bài kiểm tra trừ khi bạn khởi tạo danh sách của mình trong phương thức @Before setUp () giống như ở đây mockList = mock (ArrayList.class); spyList = spy (ArrayList.class); và xóa mô hình và chú thích gián điệp được đề xuất ở đây. Tôi đã thử nghiệm nó và các bài kiểm tra của tôi hiện đang vượt qua.
The_Martian

17

Phiên bản TL; DR,

Với giả lập , nó tạo ra một phiên bản vỏ xương cho bạn.

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

Với gián điệp bạn một phần có thể thử trên một hiện dụ

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

Trường hợp sử dụng điển hình cho Spy: lớp có một phương thức khởi tạo được tham số hóa, bạn muốn tạo đối tượng trước.


14

Tôi đã tạo một ví dụ có thể chạy được tại đây https://www.surasint.com/mockito-with-spy/

Tôi sao chép một số nó ở đây.

Nếu bạn có một cái gì đó giống như mã này:

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

Bạn có thể không cần gián điệp vì bạn chỉ có thể giả mạo DepositMoneyService và WithdrawMoneyService.

Nhưng với một số mã kế thừa, sự phụ thuộc nằm trong mã như sau:

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

Có, bạn có thể thay đổi mã đầu tiên nhưng sau đó API được thay đổi. Nếu phương pháp này đang được nhiều nơi sử dụng thì bạn phải thay đổi tất cả.

Cách khác là bạn có thể trích xuất phần phụ thuộc như sau:

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

Sau đó, bạn có thể sử dụng gián điệp để tiêm phụ thuộc như sau:

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

Chi tiết hơn trong liên kết ở trên.


13

Nơi tốt nhất để bắt đầu có lẽ là tài liệu cho mockito .

Một lưu ý chung, mô hình mockito cho phép bạn tạo các bản khai.

Ví dụ, bạn sẽ tạo một phương thức sơ khai nếu phương thức đó thực hiện một phép toán đắt tiền. Giả sử, nó nhận được một kết nối cơ sở dữ liệu, lấy một giá trị từ cơ sở dữ liệu và trả lại nó cho người gọi. Việc nhận kết nối db có thể mất 30 giây, làm chậm quá trình thực thi thử nghiệm của bạn đến mức bạn có thể sẽ chuyển đổi ngữ cảnh (hoặc ngừng chạy thử nghiệm).

Nếu logic bạn đang kiểm tra không quan tâm đến kết nối cơ sở dữ liệu thì bạn có thể thay thế phương thức đó bằng một sơ khai trả về một giá trị được mã hóa cứng.

Mockito spy cho phép bạn kiểm tra xem một phương thức có gọi các phương thức khác hay không. Điều này có thể rất hữu ích khi cố gắng lấy mã kế thừa đang được thử nghiệm.

Sẽ rất hữu ích nếu bạn đang thử nghiệm một phương pháp hoạt động thông qua các tác dụng phụ, thì bạn sẽ sử dụng một mô hình gián điệp. Ủy quyền này gọi đến đối tượng thực và cho phép bạn xác minh việc gọi phương thức, số lần được gọi, v.v.


7

Nói ngắn gọn:

@Spy@Mockđược sử dụng nhiều trong việc thử nghiệm mã, nhưng các nhà phát triển thường nhầm lẫn trong trường hợp khi nào sử dụng một trong số chúng và do đó các nhà phát triển kết thúc việc sử dụng @Mockđể được an toàn.

  • Sử dụng @Mockkhi bạn muốn chỉ kiểm tra chức năng bên ngoài mà không thực sự gọi phương thức đó.
  • Sử dụng @Spykhi bạn muốn kiểm tra chức năng bên ngoài + bên trong với chính phương thức đang được gọi.

Dưới đây là ví dụ mà tôi đã thực hiện kịch bản Bầu cử20xx ở Mỹ.

Các phiếu bầu có thể được chia theo VotersOfBelow21VotersOfABove21.

Cuộc thăm dò lý tưởng của Exit nói rằng Trump sẽ giành chiến thắng trong cuộc bầu cử bởi vì VotersOfBelow21VotersOfABove21cả hai sẽ bỏ phiếu cho Trump nói rằng " Chúng tôi đã bầu Tổng thống Trump "

Nhưng đây không phải là kịch bản thực:

Cử tri ở cả hai nhóm tuổi đã bỏ phiếu cho Trump vì họ không có sự lựa chọn hiệu quả nào khác ngoài ông Trump.

Vì vậy, làm thế nào để bạn kiểm tra nó ??

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

}

Bây giờ Lưu ý trong hai lớp học đầu tiên ở trên, cả hai nhóm tuổi nói rằng họ không có lựa chọn nào tốt hơn so với át chủ bài. Điều đó rõ ràng có nghĩa là họ bỏ phiếu cho Trump chỉ vì họ không có sự lựa chọn.

Bây giờ người ElectionOfYear20XX ta nói rằng Trump đã thắng vì cả hai nhóm tuổi đã bỏ phiếu cho anh ta một cách áp đảo.

Nếu chúng tôi kiểm tra ElectionOfYear20XXvới @Mock, thì chúng tôi có thể không biết được lý do thực sự tại sao Trump thắng, chúng tôi sẽ chỉ kiểm tra lý do bên ngoài.

Nếu chúng ta kiểm tra ElectionOfYear20XXbằng @Spy, thì chúng ta sẽ biết được lý do thực sự tại sao Trump thắng với kết quả thăm dò ý kiến ​​từ bên ngoài, tức là bên trong + bên ngoài.


ELectionOfYear20XX_TestLớp chúng tôi :

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

}

Điều này sẽ chỉ xuất ra kết quả kiểm tra logic, tức là kiểm tra bên ngoài:

We elected President Trump 
We elected President Trump 

Kiểm tra với @Spybên ngoài cũng như bên trong với lệnh gọi phương pháp thực tế.

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

}

Đầu ra:

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

Tôi thích sự đơn giản của đề xuất này:

  • Nếu bạn muốn an toàn và tránh gọi các dịch vụ bên ngoài và chỉ muốn kiểm tra tính logic bên trong của thiết bị, thì hãy sử dụng mô hình .
  • Nếu bạn muốn gọi dịch vụ bên ngoài và thực hiện việc gọi các phần phụ thuộc thực sự, hay nói đơn giản, bạn muốn chạy chương trình như nó vốn có và chỉ khai thác các phương thức cụ thể, thì hãy sử dụng spy .

Nguồn: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Một sự khác biệt phổ biến là:

  • Nếu bạn muốn khai thác trực tiếp (các) phương thức của một phụ thuộc, thì hãy Giả lập phụ thuộc đó.
  • Nếu bạn muốn còn sơ khai dữ liệu trong một sự phụ thuộc để tất cả các phương pháp của nó trả lại thử nghiệm giá trị mà bạn cần, sau đó Spy rằng sự phụ thuộc.

Lưu ý rằng Spy và Mock luôn dành cho các phần phụ thuộc chứ không phải cho hệ thống đang thử nghiệm.
leo9r
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.