Mockito - ฉันเข้าใจว่าสายลับเรียกวิธีการจริงบนวัตถุในขณะที่การล้อเลียนเรียกวิธีการบนวัตถุคู่ นอกจากนี้ควรหลีกเลี่ยงสายลับเว้นแต่จะมีกลิ่นรหัส อย่างไรก็ตามสายลับทำงานอย่างไรและฉันควรใช้มันจริงเมื่อใด ต่างจากล้อเลียนอย่างไร?
Mockito - ฉันเข้าใจว่าสายลับเรียกวิธีการจริงบนวัตถุในขณะที่การล้อเลียนเรียกวิธีการบนวัตถุคู่ นอกจากนี้ควรหลีกเลี่ยงสายลับเว้นแต่จะมีกลิ่นรหัส อย่างไรก็ตามสายลับทำงานอย่างไรและฉันควรใช้มันจริงเมื่อใด ต่างจากล้อเลียนอย่างไร?
คำตอบ:
ในทางเทคนิคการพูดทั้ง "ล้อเลียน" และ "สายลับ" เป็น "การทดสอบคู่" แบบพิเศษ
Mockito โชคไม่ดีที่ทำให้ความแตกต่างแปลกไป
การเยาะเย้ยใน mockito คือการเยาะเย้ยตามปกติในกรอบการเยาะเย้ยอื่น ๆ (อนุญาตให้คุณหยุดการเรียกใช้นั่นคือส่งคืนค่าเฉพาะออกจากการเรียกใช้เมธอด)
สายลับในม็อกกิโตคือการล้อเลียนบางส่วนในกรอบการเยาะเย้ยอื่น ๆ (ส่วนหนึ่งของวัตถุจะถูกล้อเลียนและส่วนหนึ่งจะใช้การเรียกใช้วิธีการจริง)
ทั้งสองอย่างสามารถใช้เพื่อจำลองวิธีการหรือฟิลด์ ความแตกต่างก็คือในการล้อเลียนคุณกำลังสร้างวัตถุจำลองหรือของปลอมที่สมบูรณ์ในขณะที่สอดแนมมีของจริงและคุณเพียงแค่สอดแนมหรือขัดขวางวิธีการเฉพาะของมัน
แน่นอนว่าในขณะที่อยู่ในวัตถุสอดแนมเนื่องจากเป็นวิธีการจริงเมื่อคุณไม่ได้ขัดขวางวิธีการนั้นจะเรียกลักษณะการทำงานของวิธีการจริง หากคุณต้องการเปลี่ยนและเยาะเย้ยวิธีการคุณจะต้องตัดมันออก
ลองพิจารณาตัวอย่างด้านล่างเพื่อเปรียบเทียบ
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));
}
}
เมื่อคุณใช้คำพูดล้อเลียนหรือสอดแนม? หากคุณต้องการความปลอดภัยและหลีกเลี่ยงการโทรหาบริการภายนอกและเพียงแค่ต้องการทดสอบตรรกะภายในเครื่องให้ใช้การจำลอง หากคุณต้องการเรียกใช้บริการภายนอกและทำการเรียกการพึ่งพาจริงหรือพูดง่ายๆคือคุณต้องการเรียกใช้โปรแกรมตามที่เป็นอยู่และเพียงแค่ขีดฆ่าวิธีการเฉพาะจากนั้นใช้สปาย นั่นคือความแตกต่างระหว่างสายลับและการล้อเลียนในม็อกกิโตะ
TL; DR เวอร์ชัน
ด้วยการเยาะเย้ยจะสร้างตัวอย่างเปลือกกระดูกที่เปลือยเปล่าให้คุณ
List<String> mockList = Mockito.mock(ArrayList.class);
ด้วยสายลับคุณสามารถล้อเลียนอินสแตนซ์ที่มีอยู่ได้บางส่วน
List<String> spyList = Mockito.spy(new ArrayList<String>());
กรณีการใช้งานทั่วไปสำหรับ 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();
รายละเอียดเพิ่มเติมในลิงค์ด้านบน
สถานที่ที่ดีที่สุดในการเริ่มต้นอาจจะเป็นเอกสารสำหรับ Mockito
ในหมายเหตุทั่วไปการจำลอง mockito ช่วยให้คุณสร้างต้นขั้ว
คุณจะต้องสร้างวิธีการต้นขั้วถ้าเช่นวิธีนั้นมีการดำเนินการที่มีราคาแพง สมมติว่าได้รับการเชื่อมต่อฐานข้อมูลดึงค่าจากฐานข้อมูลและส่งคืนให้กับผู้โทร การเชื่อมต่อฐานข้อมูลอาจใช้เวลา 30 วินาทีทำให้การดำเนินการทดสอบของคุณช้าลงจนถึงจุดที่คุณอาจเปลี่ยนบริบท (หรือหยุดดำเนินการทดสอบ)
หากตรรกะที่คุณกำลังทดสอบไม่สนใจเกี่ยวกับการเชื่อมต่อฐานข้อมูลคุณสามารถแทนที่วิธีนั้นด้วยต้นขั้วซึ่งส่งคืนค่าฮาร์ดโค้ด
สายลับม็อกกิโตช่วยให้คุณตรวจสอบว่าวิธีเรียกใช้วิธีอื่นหรือไม่ สิ่งนี้จะมีประโยชน์มากเมื่อพยายามรับรหัสเดิมที่อยู่ระหว่างการทดสอบ
เป็นประโยชน์หากคุณกำลังทดสอบวิธีการที่ได้ผลข้างเคียงจากนั้นคุณจะใช้สายลับม็อกกิโต สิ่งนี้จะเรียกร้องไปยังวัตถุจริงและช่วยให้คุณตรวจสอบการเรียกใช้เมธอดจำนวนครั้งที่เรียก ฯลฯ
ในระยะสั้น:
@Spy
และ@Mock
ถูกใช้อย่างมากในการทดสอบโค้ด แต่นักพัฒนาอาจสับสนในกรณีที่ควรใช้โค้ดเหล่านี้เมื่อใดจึงทำให้นักพัฒนาต้องใช้@Mock
เพื่อความปลอดภัย
@Mock
เมื่อคุณต้องการทดสอบการทำงานภายนอก
โดยไม่ต้องเรียกใช้เมธอดนั้นจริงๆ@Spy
เมื่อคุณต้องการทดสอบการทำงานภายนอก + ภายในด้วยวิธีการที่เรียกด้านล่างนี้คือตัวอย่างที่ฉันได้ใช้สถานการณ์Election20xxในอเมริกา
ผู้มีสิทธิเลือกตั้งสามารถแบ่งออกตามและVotersOfBelow21
VotersOfABove21
ผลสำรวจทางออกในอุดมคติระบุว่าทรัมป์จะชนะการเลือกตั้งเพราะ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
ฉันชอบความเรียบง่ายของคำแนะนำนี้:
- หากคุณต้องการที่จะปลอดภัยและหลีกเลี่ยงการเรียกบริการภายนอกและเพียงแค่ต้องการที่จะทดสอบภายในตรรกะของหน่วยแล้วใช้จำลอง
- หากคุณต้องการที่จะเรียกใช้บริการจากภายนอกและดำเนินการเรียกร้องการอ้างอิงจริงหรือเพียงแค่บอกว่าคุณต้องการที่จะเรียกใช้โปรแกรมที่เป็นต้นขั้วและวิธีการที่เฉพาะเจาะจงเพียงแล้วใช้สายลับ
ที่มา: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
ความแตกต่างทั่วไปคือ:
- หากคุณต้องการตัดเมธอดของการอ้างอิงโดยตรงให้จำลองการอ้างอิงนั้น
- หากคุณต้องการตัดข้อมูลในการพึ่งพาเพื่อให้วิธีการทั้งหมดส่งคืนค่าการทดสอบที่คุณต้องการให้สอดแนมการพึ่งพานั้น