Mockito - @Spy vs @Mock


99

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


2
มีความเป็นไปได้ที่จะซ้ำกันของmockito mock vs. spy
rds

คำตอบ:


88

ในทางเทคนิคการพูดทั้ง "ล้อเลียน" และ "สายลับ" เป็น "การทดสอบคู่" แบบพิเศษ

Mockito โชคไม่ดีที่ทำให้ความแตกต่างแปลกไป

การเยาะเย้ยใน mockito คือการเยาะเย้ยตามปกติในกรอบการเยาะเย้ยอื่น ๆ (อนุญาตให้คุณหยุดการเรียกใช้นั่นคือส่งคืนค่าเฉพาะออกจากการเรียกใช้เมธอด)

สายลับในม็อกกิโตคือการล้อเลียนบางส่วนในกรอบการเยาะเย้ยอื่น ๆ (ส่วนหนึ่งของวัตถุจะถูกล้อเลียนและส่วนหนึ่งจะใช้การเรียกใช้วิธีการจริง)


41

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

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

ลองพิจารณาตัวอย่างด้านล่างเพื่อเปรียบเทียบ

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

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


คำตอบที่ดี แต่จะส่งการตรวจสอบข้อผิดพลาดเท่านั้นและจะไม่เรียกใช้การทดสอบเว้นแต่คุณจะเริ่มต้นรายการของคุณในวิธีการ @Before setUp () เหมือนที่นี่ mockList = mock (ArrayList.class); spyList = สายลับ (ArrayList.class); และลบคำอธิบายประกอบจำลองและสอดแนมที่แนะนำที่นี่ ฉันได้ทดสอบแล้วและตอนนี้การทดสอบของฉันผ่านแล้ว
The_Martian

17

TL; DR เวอร์ชัน

ด้วยการเยาะเย้ยจะสร้างตัวอย่างเปลือกกระดูกที่เปลือยเปล่าให้คุณ

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

ด้วยสายลับคุณสามารถล้อเลียนอินสแตนซ์ที่มีอยู่ได้บางส่วน

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

กรณีการใช้งานทั่วไปสำหรับ Spy: คลาสนี้มีตัวสร้างพารามิเตอร์ที่คุณต้องการสร้างวัตถุก่อน


14

ฉันได้สร้างตัวอย่างที่สามารถรันได้ที่นี่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();

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


13

สถานที่ที่ดีที่สุดในการเริ่มต้นอาจจะเป็นเอกสารสำหรับ Mockito

ในหมายเหตุทั่วไปการจำลอง mockito ช่วยให้คุณสร้างต้นขั้ว

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

หากตรรกะที่คุณกำลังทดสอบไม่สนใจเกี่ยวกับการเชื่อมต่อฐานข้อมูลคุณสามารถแทนที่วิธีนั้นด้วยต้นขั้วซึ่งส่งคืนค่าฮาร์ดโค้ด

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

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


7

ในระยะสั้น:

@Spyและ@Mockถูกใช้อย่างมากในการทดสอบโค้ด แต่นักพัฒนาอาจสับสนในกรณีที่ควรใช้โค้ดเหล่านี้เมื่อใดจึงทำให้นักพัฒนาต้องใช้@Mockเพื่อความปลอดภัย

  • ใช้@Mockเมื่อคุณต้องการทดสอบการทำงานภายนอก โดยไม่ต้องเรียกใช้เมธอดนั้นจริงๆ
  • ใช้@Spyเมื่อคุณต้องการทดสอบการทำงานภายนอก + ภายในด้วยวิธีการที่เรียก

ด้านล่างนี้คือตัวอย่างที่ฉันได้ใช้สถานการณ์Election20xxในอเมริกา

ผู้มีสิทธิเลือกตั้งสามารถแบ่งออกตามและVotersOfBelow21VotersOfABove21

ผลสำรวจทางออกในอุดมคติระบุว่าทรัมป์จะชนะการเลือกตั้งเพราะVotersOfBelow21และVotersOfABove21ทั้งคู่จะลงคะแนนให้คนที่กล้าหาญโดยกล่าวว่า " เราเลือกประธานาธิบดีทรัมป์ "

แต่นี่ไม่ใช่สถานการณ์จริง:

ผู้มีสิทธิเลือกตั้งทั้งสองกลุ่มอายุโหวตให้ทรัมป์เพราะพวกเขาไม่มีทางเลือกอื่นที่มีประสิทธิภาพนอกจากนายทรัมป์

แล้วคุณจะทดสอบได้อย่างไร ??

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

}

ตอนนี้สังเกตในสองชั้นเรียนแรกทั้งสองกลุ่มอายุต่างบอกว่าพวกเขาไม่มีทางเลือกที่ดีไปกว่าคนที่กล้าหาญ ซึ่งหมายความอย่างชัดเจนว่าพวกเขาลงคะแนนให้ทรัมป์เพียงเพราะพวกเขาไม่มีทางเลือก

ตอนนี้ElectionOfYear20XX บอกว่าทรัมป์ชนะเพราะทั้งสองกลุ่มอายุโหวตให้เขาอย่างท่วมท้น

หากเราจะทดสอบElectionOfYear20XXด้วย @Mock เราอาจไม่สามารถหาเหตุผลที่แท้จริงว่าทำไมทรัมป์ถึงชนะเราจะทดสอบเหตุผลภายนอก

หากเราทดสอบElectionOfYear20XXด้วย @Spy เราจะได้รับเหตุผลที่แท้จริงว่าทำไมทรัมป์ถึงชนะด้วยผลการสำรวจการออกจากภายนอกนั่นคือภายใน + ภายนอก


ELectionOfYear20XX_Testชั้นเรียนของเรา:

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

}

สิ่งนี้ควรส่งออกเพียงผลการทดสอบตรรกะเช่นการตรวจสอบภายนอก:

We elected President Trump 
We elected President Trump 

การทดสอบกับ@Spyภายนอกและภายในด้วยการเรียกใช้วิธีการจริง

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

}

เอาท์พุต:

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

ฉันชอบความเรียบง่ายของคำแนะนำนี้:

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

ที่มา: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

ความแตกต่างทั่วไปคือ:

  • หากคุณต้องการตัดเมธอดของการอ้างอิงโดยตรงให้จำลองการอ้างอิงนั้น
  • หากคุณต้องการตัดข้อมูลในการพึ่งพาเพื่อให้วิธีการทั้งหมดส่งคืนค่าการทดสอบที่คุณต้องการให้สอดแนมการพึ่งพานั้น

โปรดทราบว่า Spy และ Mock มีไว้สำหรับการอ้างอิงเสมอไม่ใช่สำหรับระบบที่อยู่ระหว่างการทดสอบ
leo9r
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.