Sự khác biệt giữa chế nhạo và gián điệp khi sử dụng Mockito là gì?


137

Điều gì sẽ là một trường hợp sử dụng cho việc sử dụng một điệp viên Mockito?

Dường như với tôi, mọi trường hợp sử dụng gián điệp đều có thể được xử lý bằng một bản giả, sử dụng callRealMethod.

Một sự khác biệt tôi có thể thấy là nếu bạn muốn hầu hết các cuộc gọi phương thức là thật, nó sẽ lưu một số dòng mã để sử dụng giả so với gián điệp. Là nó hay tôi đang thiếu hình ảnh lớn hơn?

Câu trả lời:


100

Câu trả lời là trong tài liệu :

Giả một phần thực (Kể từ 1.8.0)

Cuối cùng, sau nhiều cuộc tranh luận và thảo luận nội bộ về danh sách gửi thư, hỗ trợ giả một phần đã được thêm vào Mockito. Trước đây chúng tôi coi mock một phần là mùi mã. Tuy nhiên, chúng tôi đã tìm thấy một trường hợp sử dụng hợp pháp cho giả một phần.

Trước khi phát hành, 1.8 spy () không tạo ra một phần giả thực sự và nó gây nhầm lẫn cho một số người dùng. Đọc thêm về gián điệp: ở đây hoặc trong javadoc cho phương thức gián điệp (Object).

callRealMethod()đã được giới thiệu sau đó spy(), nhưng dĩ nhiên điệp viên () bị bỏ lại ở đó, để đảm bảo khả năng tương thích ngược.

Mặt khác, bạn đã đúng: tất cả các phương pháp của một điệp viên là có thật trừ khi còn sơ khai. Tất cả các phương pháp của một giả đều được khai thác trừ khi callRealMethod()được gọi. Nói chung, tôi thích sử dụng hơn callRealMethod(), vì nó không bắt buộc tôi phải sử dụng doXxx().when()thành ngữ thay vì truyền thốngwhen().thenXxx()


Vấn đề với việc thích giả mạo hơn trong các trường hợp này, là khi lớp sử dụng một thành viên không được tiêm vào nó (nhưng được khởi tạo cục bộ) và sau đó được sử dụng bởi phương thức "thực"; trong bản giả, thành viên sẽ được khởi tạo thành giá trị Java mặc định của nó, điều này có thể gây ra hành vi sai hoặc thậm chí là NullPulumException. Cách để vượt qua điều này là thêm một phương thức "init" và sau đó "thực sự" gọi nó, nhưng điều đó có vẻ hơi quá sức đối với tôi.
Eyal Roth

Từ tài liệu: "gián điệp nên được sử dụng cẩn thận và đôi khi, ví dụ như khi xử lý mã kế thừa." Không gian thử nghiệm đơn vị chịu quá nhiều cách để làm điều tương tự.
gdbj

89

Sự khác biệt giữa Spy và Mock

Khi Mockito tạo ra một bản giả - nó làm như vậy từ Class of a Type, không phải từ một thể hiện thực tế. Giả chỉ đơn giản là tạo ra một thể hiện vỏ trần của Class, hoàn toàn là công cụ để theo dõi các tương tác với nó. Mặt khác, điệp viên sẽ bọc một ví dụ hiện có. Nó vẫn sẽ hoạt động giống như trường hợp thông thường - sự khác biệt duy nhất là nó cũng sẽ được sử dụng để theo dõi tất cả các tương tác với nó.

Trong ví dụ sau - chúng tôi tạo một mô hình của lớp ArrayList:

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

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

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

Như bạn có thể thấy - việc thêm một phần tử vào danh sách giả định không thực sự thêm bất cứ thứ gì - nó chỉ gọi phương thức mà không có tác dụng phụ nào khác. Mặt khác, một gián điệp sẽ hành xử khác đi - nó thực sự sẽ gọi việc thực hiện thực sự của phương thức add và thêm phần tử vào danh sách bên dưới:

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

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

Ở đây chúng ta chắc chắn có thể nói rằng phương thức bên trong thực sự của đối tượng đã được gọi bởi vì khi bạn gọi phương thức size () bạn có kích thước là 1, nhưng phương thức size () này không bị chế giễu! Vậy 1 đến từ đâu? Phương thức kích thước thực () bên trong được gọi là kích thước () không bị chế giễu (hoặc còn sơ khai) và do đó chúng ta có thể nói rằng mục nhập đã được thêm vào đối tượng thực.

Nguồn: http://www.baeldung.com/mockito-spy + tự ghi chú.


1
Ý bạn là size () trả về 1?
đen

Trong ví dụ đầu tiên, tại sao mockedList.size()lại quay trở lại 0nếu phương thức đó không bị loại bỏ? Có phải đó chỉ là một giá trị mặc định cho kiểu trả về của phương thức?
mike

@mike: mockedList.size()trả về intgiá trị mặc định và intlà 0 trong Java. Nếu bạn cố gắng thực hiện assertEquals(0, mockedList.size());sau mockedList.clear();, kết quả vẫn như cũ.
realPK

2
Câu trả lời này được viết tốt và đơn giản và giúp tôi cuối cùng hiểu được sự khác biệt giữa giả và gián điệp. Đẹp một.
Pesa

38

Nếu có một đối tượng với 8 phương thức và bạn có một bài kiểm tra mà bạn muốn gọi 7 phương thức thực và bỏ sơ khai một phương thức, bạn có hai tùy chọn:

  1. Sử dụng một giả lập, bạn sẽ phải thiết lập nó bằng cách gọi 7 callRealMethod và khai thác một phương thức
  2. Sử dụng một spy bạn phải thiết lập nó bằng cách khai thác một phương thức

Các tài liệu chính thức vềdoCallRealMethod khuyến nghị sử dụng một gián điệp cho giả một phần.

Xem thêm gián điệp javadoc (Object) để tìm hiểu thêm về giả lập một phần. Mockito.spy () là một cách được đề xuất để tạo giả một phần. Lý do là nó đảm bảo các phương thức thực được gọi chống lại đối tượng được xây dựng chính xác bởi vì bạn chịu trách nhiệm xây dựng đối tượng được truyền cho phương thức spy ().


5

Spy có thể hữu ích khi bạn muốn tạo các bài kiểm tra đơn vị cho mã kế thừa .

Tôi đã tạo một ví dụ có thể chạy ở đây https://www.surasint.com/mockito-with-spy/ , tôi sao chép một số đâ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ả lập DepositMoneyService và WithdrawMoneyService.

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

    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 sử dụng bởi nhiều nơi, bạn phải thay đổi tất cả chúng.

Thay thế là bạn có thể trích xuất sự phụ thuộc ra như thế này:

    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ư thế này:

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.


0

Mocklà một đối tượng đôi trần. Đối tượng này có cùng chữ ký phương thức nhưng thực hiện trống và trả về giá trị mặc định - 0 và null

Spylà một đối tượng nhân bản kép. Đối tượng mới được nhân bản dựa trên một đối tượng thực nhưng bạn có khả năng giả định nó

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

[Kiểm tra loại kép]

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.