อะไรคือความแตกต่างระหว่างการล้อเลียนและการสอดแนมเมื่อใช้ Mockito?


142

อะไรคือกรณีการใช้งานสำหรับการใช้สายลับ Mockito?

สำหรับฉันแล้วดูเหมือนว่าทุกกรณีการใช้งานสายลับสามารถจัดการได้ด้วยการเยาะเย้ยโดยใช้ callRealMethod

ความแตกต่างอย่างหนึ่งที่ฉันเห็นคือถ้าคุณต้องการให้การโทรส่วนใหญ่เป็นของจริงมันจะบันทึกโค้ดบางบรรทัดเพื่อใช้จำลองกับสายลับ เป็นอย่างนั้นหรือฉันพลาดภาพใหญ่กว่านี้

คำตอบ:


103

คำตอบอยู่ในเอกสาร :

ล้อเลียนจริงบางส่วน (ตั้งแต่ 1.8.0)

ในที่สุดหลังจากการอภิปรายและการอภิปรายภายในหลายครั้งเกี่ยวกับรายชื่อผู้รับจดหมายการสนับสนุนการล้อเลียนบางส่วนได้ถูกเพิ่มเข้ามาใน Mockito ก่อนหน้านี้เราถือว่าการล้อเลียนบางส่วนเป็นกลิ่นรหัส อย่างไรก็ตามเราพบกรณีการใช้งานที่ถูกต้องสำหรับการล้อเลียนบางส่วน

ก่อนที่จะปล่อยสายลับ 1.8 () ไม่ได้ผลิตล้อเลียนบางส่วนจริงและทำให้ผู้ใช้บางคนสับสน อ่านเพิ่มเติมเกี่ยวกับการสอดแนม: ที่นี่หรือใน javadoc สำหรับวิธีการสอดแนม (Object)

callRealMethod()ได้รับการแนะนำหลังจากspy()นั้น แต่สายลับ () ถูกทิ้งไว้ที่นั่นแน่นอนเพื่อให้แน่ใจว่าเข้ากันได้แบบย้อนหลัง

มิฉะนั้นคุณพูดถูก: วิธีการทั้งหมดของสายลับเป็นเรื่องจริงเว้นแต่จะถูกขัดขวาง วิธีการจำลองทั้งหมดจะถูกตัดออกเว้นแต่callRealMethod()จะถูกเรียก โดยทั่วไปแล้วฉันชอบใช้callRealMethod()มากกว่าเพราะมันไม่ได้บังคับให้ฉันใช้doXxx().when()สำนวนแทนแบบดั้งเดิมwhen().thenXxx()


ปัญหาเกี่ยวกับการชอบล้อเลียนมากกว่าสายลับในกรณีเหล่านี้คือเมื่อชั้นเรียนใช้สมาชิกที่ไม่ได้ฉีดเข้าไปในนั้น (แต่เริ่มต้นในพื้นที่) และใช้วิธีการ "จริง" ในภายหลัง ในการจำลองสมาชิกจะเริ่มต้นเป็นค่า Java เริ่มต้นซึ่งอาจทำให้เกิดพฤติกรรมที่ไม่ถูกต้องหรือแม้แต่ NullPointerException วิธีส่งผ่านคือเพิ่มเมธอด "init" แล้วเรียก "จริงๆ" แต่ดูเหมือนจะหักโหมเกินไป
Eyal Roth

จากเอกสาร: "ควรใช้สายลับอย่างระมัดระวังและเป็นครั้งคราวเช่นเมื่อต้องจัดการกับรหัสเดิม" พื้นที่ทดสอบหน่วยมีปัญหาในการทำสิ่งเดียวกันมากเกินไป
gdbj

95

ความแตกต่างระหว่าง Spy และ Mock

เมื่อ Mockito สร้างการเยาะเย้ย - มันทำจาก Class of a Type ไม่ใช่จากอินสแตนซ์จริง การเยาะเย้ยเพียงแค่สร้างอินสแตนซ์เปลือกของคลาสที่เปลือยเปล่าโดยใช้เครื่องมือทั้งหมดเพื่อติดตามการโต้ตอบกับคลาส ในทางกลับกันสายลับจะห่อหุ้มอินสแตนซ์ที่มีอยู่ มันจะยังคงทำงานในลักษณะเดียวกับอินสแตนซ์ปกติ - ความแตกต่างเพียงอย่างเดียวคือมันจะถูกใช้เพื่อติดตามการโต้ตอบทั้งหมดที่มีด้วย

ในตัวอย่างต่อไปนี้ - เราสร้างการจำลองคลาส ArrayList:

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

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

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

อย่างที่คุณเห็น - การเพิ่มองค์ประกอบลงในรายการจำลองนั้นไม่ได้เพิ่มอะไรเลย - เพียงแค่เรียกใช้เมธอดโดยไม่มีผลข้างเคียงอื่น ๆ ในทางกลับกันสายลับจะทำงานแตกต่างออกไป - มันจะเรียกการใช้งานจริงของวิธีการเพิ่มและเพิ่มองค์ประกอบลงในรายการพื้นฐาน:

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

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

ที่นี่เราสามารถพูดได้อย่างแน่นอนว่าเมธอดภายในจริงของอ็อบเจกต์ถูกเรียกเพราะเมื่อคุณเรียกเมธอด size () คุณจะได้ขนาดเป็น 1 แต่เมธอด size () นี้ไม่ได้ถูกเยาะเย้ย! 1 มาจากไหน? วิธีการขนาดจริงภายใน () เรียกว่าขนาด () ไม่ได้ถูกเยาะเย้ย (หรือถูกดึงออก) และด้วยเหตุนี้เราจึงสามารถพูดได้ว่ารายการนั้นถูกเพิ่มลงในวัตถุจริง

ที่มา: http://www.baeldung.com/mockito-spy + บันทึกด้วยตนเอง


1
คุณไม่ได้หมายถึงขนาด () ส่งคืน 1?
ดำ

ในตัวอย่างแรกเหตุใดจึงmockedList.size()ส่งคืน0หากวิธีนั้นยังไม่ถูกตัดออกด้วย นั่นเป็นเพียงค่าเริ่มต้นที่กำหนดประเภทการส่งคืนของวิธีการหรือไม่
ไมค์

@mike: mockedList.size()ส่งคืนค่าintและค่าเริ่มต้นintเป็น 0 ใน Java หากคุณพยายามดำเนินการassertEquals(0, mockedList.size());หลังจากmockedList.clear();นั้นผลลัพธ์จะยังคงเหมือนเดิม
realPK

2
คำตอบนี้เขียนได้ดีและเรียบง่ายและช่วยให้ฉันเข้าใจความแตกต่างระหว่างการเยาะเย้ยและสายลับได้ในที่สุด ทำได้ดีนี่.
Pesa

38

หากมีออบเจ็กต์ที่มี 8 วิธีและคุณมีการทดสอบที่คุณต้องการเรียกใช้วิธีจริง 7 วิธีและตัดวิธีการหนึ่งคุณมีสองทางเลือก:

  1. การใช้การเยาะเย้ยคุณจะต้องตั้งค่าโดยการเรียกใช้ callRealMethod 7 วิธีและตัดวิธีการเดียว
  2. การใช้spyคุณจะต้องตั้งขึ้นโดย stubbing วิธีการหนึ่ง

เอกสารอย่างเป็นทางการเกี่ยวกับการdoCallRealMethodแนะนำให้ใช้สายลับให้ mocks บางส่วน

ดู javadoc spy (Object) เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการล้อเลียนบางส่วน Mockito.spy () เป็นวิธีที่แนะนำในการสร้างล้อเลียนบางส่วน เหตุผลก็คือรับประกันว่าวิธีการจริงถูกเรียกใช้กับวัตถุที่สร้างขึ้นอย่างถูกต้องเนื่องจากคุณมีหน้าที่รับผิดชอบในการสร้างวัตถุที่ส่งผ่านไปยังวิธีการสอดแนม ()


7

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

คุณอาจไม่จำเป็นต้องมีสายลับเพราะคุณสามารถล้อเลียน 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();
    }

จากนั้นคุณสามารถใช้สายลับที่ฉีดการพึ่งพาได้ดังนี้:

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

รายละเอียดเพิ่มเติมในลิงค์ด้านบน


4

[ทดสอบสองประเภท]

Mock เทียบกับ 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();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.