ฉันได้อ่านบทความต่าง ๆ เกี่ยวกับการเยาะเย้ยและการขัดถูในการทดสอบรวมถึงMocks Arsenal ของมาร์ตินฟาวเลอร์ไม่ใช่ Stubแต่ก็ยังไม่เข้าใจความแตกต่าง
ฉันได้อ่านบทความต่าง ๆ เกี่ยวกับการเยาะเย้ยและการขัดถูในการทดสอบรวมถึงMocks Arsenal ของมาร์ตินฟาวเลอร์ไม่ใช่ Stubแต่ก็ยังไม่เข้าใจความแตกต่าง
คำตอบ:
ต้นขั้ว
ฉันเชื่อว่าความแตกต่างที่ยิ่งใหญ่ที่สุดคือต้นขั้วที่คุณเขียนไปแล้วพร้อมกับพฤติกรรมที่กำหนดไว้ล่วงหน้า ดังนั้นคุณจะมีคลาสที่ใช้การพึ่งพา (คลาสนามธรรมหรืออินเทอร์เฟซที่เป็นไปได้มากที่สุด) ที่คุณแกล้งทำเพื่อวัตถุประสงค์ในการทดสอบและวิธีการต่างๆ พวกเขาจะไม่ทำอะไรแฟนซีและคุณจะได้เขียนรหัส stubbed สำหรับมันนอกการทดสอบของคุณ
เยาะเย้ย
การเยาะเย้ยเป็นสิ่งที่เป็นส่วนหนึ่งของการทดสอบของคุณคุณต้องตั้งค่าตามความคาดหวังของคุณ การเยาะเย้ยไม่ได้ตั้งค่าในวิธีที่กำหนดไว้ล่วงหน้าเพื่อให้คุณมีรหัสที่ทำในการทดสอบของคุณ การกำหนด Mocks ในลักษณะที่รันไทม์เนื่องจากรหัสที่กำหนดความคาดหวังต้องทำงานก่อนที่จะทำอะไร
ความแตกต่างระหว่าง Mocks และ Stubs
การทดสอบที่เขียนด้วย mocks มักจะเป็นไปตามinitialize -> set expectations -> exercise -> verify
รูปแบบการทดสอบ initialize -> exercise -> verify
ในขณะที่ต้นขั้วที่เขียนไว้ล่วงหน้าจะปฏิบัติตาม
ความคล้ายคลึงกันระหว่าง Mocks และ Stubs
จุดประสงค์ของทั้งคู่คือเพื่อกำจัดการทดสอบการพึ่งพาทั้งหมดของคลาสหรือฟังก์ชันดังนั้นการทดสอบของคุณจะเน้นและเรียบง่ายขึ้นในสิ่งที่พวกเขากำลังพยายามพิสูจน์
มีคำจำกัดความของวัตถุหลายอย่างที่ไม่จริง คำทั่วไปคือการทดสอบคู่ นี้รวมระยะ: หุ่น , ปลอม , กุด , เยาะเย้ย
อ้างอิงจากบทความของ Martin Fowler :
- วัตถุจำลองจะถูกส่งผ่านไป แต่ไม่เคยใช้จริง โดยปกติจะใช้เพื่อเติมรายการพารามิเตอร์
- วัตถุปลอมมีการใช้งานจริง แต่มักจะใช้ทางลัดบางอย่างซึ่งทำให้พวกเขาไม่เหมาะสำหรับการผลิต (ฐานข้อมูลในหน่วยความจำเป็นตัวอย่างที่ดี)
- สตับส์ให้คำตอบแบบกระป๋องสำหรับการโทรในระหว่างการทดสอบโดยปกติจะไม่ตอบสนองต่อสิ่งใดนอกสิ่งที่ตั้งโปรแกรมไว้สำหรับการทดสอบ สตับอาจบันทึกข้อมูลเกี่ยวกับการโทรเช่นสตับเกตเวย์อีเมลที่จดจำข้อความที่ 'ส่ง' หรืออาจเป็นเพียงแค่จำนวนข้อความที่ 'ส่ง'
- Mocksคือสิ่งที่เรากำลังพูดถึงที่นี่: วัตถุตั้งโปรแกรมล่วงหน้าด้วยความคาดหวังซึ่งเป็นรูปแบบของการโทรที่พวกเขาคาดว่าจะได้รับ
Mocks vs Stubs = การทดสอบเชิงพฤติกรรมเทียบกับการทดสอบสถานะ
ตามหลักการของการทดสอบเพียงสิ่งเดียวต่อการทดสอบอาจมีหลายสตับในการทดสอบเพียงครั้งเดียว แต่โดยทั่วไปจะมีการจำลองเพียงครั้งเดียว
ทดสอบวงจรชีวิตด้วยสตับ:
ทดสอบวงจรชีวิตด้วย mocks:
ทั้งการทดสอบ mocks และ stubs ให้คำตอบสำหรับคำถาม: ผลลัพธ์คืออะไร
การทดสอบกับ mocks ก็มีความสนใจใน: ผลที่ได้รับทำได้อย่างไร?
ต้นขั้วเป็นวัตถุปลอมง่าย มันทำให้การทดสอบดำเนินไปอย่างราบรื่น
การเยาะเย้ยเป็นต้นขั้วที่ชาญฉลาด คุณตรวจสอบการทดสอบของคุณผ่านมัน
นี่คือคำอธิบายของแต่ละคนตามด้วยตัวอย่างจริง
Dummy - API
เพียงแค่ปลอมค่าเพื่อตอบสนองความ
ตัวอย่าง : หากคุณกำลังทดสอบวิธีการเรียนที่ต้องการพารามิเตอร์บังคับจำนวนมากในตัวสร้างที่ไม่มีผลต่อการทดสอบของคุณคุณอาจสร้างวัตถุจำลองเพื่อวัตถุประสงค์ในการสร้างอินสแตนซ์ใหม่ของชั้นเรียน
ปลอม - สร้างการทดสอบการใช้งานของคลาสที่อาจมีการพึ่งพาโครงสร้างพื้นฐานภายนอกบางอย่าง (เป็นแนวปฏิบัติที่ดีที่การทดสอบหน่วยของคุณไม่ได้มีปฏิสัมพันธ์กับโครงสร้างพื้นฐานภายนอก)
ตัวอย่าง : สร้างการใช้งานปลอมสำหรับการเข้าถึงฐานข้อมูลแทนที่ด้วยการ
in-memory
รวบรวม
กุด - state-based
วิธีการที่จะกลับมาแทนที่ค่ากำหนดค่าตายตัวยังเรียกว่า
ตัวอย่าง : คลาสทดสอบของคุณขึ้นอยู่กับวิธีที่
Calculate()
ใช้เวลาในการทำ 5 นาที แทนที่จะรอ 5 นาทีคุณสามารถแทนที่การใช้งานจริงด้วย stub ที่ส่งคืนค่าฮาร์ดโค้ด ใช้เวลาเพียงเล็กน้อยเท่านั้น
เยาะเย้ย - คล้ายกันมากStub
แต่interaction-based
ไม่ใช่รัฐ ซึ่งหมายความว่าคุณไม่ได้คาดหวังว่าMock
จะส่งคืนค่าบางอย่าง แต่ให้ถือว่าลำดับของการเรียกใช้เมธอดเฉพาะ
ตัวอย่าง: คุณกำลังทดสอบคลาสการลงทะเบียนผู้ใช้ หลังจากที่โทรก็ควรจะเรียก
Save
SendConfirmationEmail
Stubs
และMocks
เป็นประเภทย่อยจริง ๆMock
ทั้งการแลกเปลี่ยนการใช้งานจริงกับการทดสอบการใช้งาน แต่ด้วยเหตุผลที่เฉพาะเจาะจงแตกต่างกัน
ในหลักสูตรcodeschool.com การทดสอบ Rails สำหรับ Zombiesพวกเขาให้คำจำกัดความของคำศัพท์นี้:
ต้นขั้ว
สำหรับการแทนที่เมธอดด้วยโค้ดที่ส่งคืนผลลัพธ์ที่ระบุ
เยาะเย้ย
ต้นขั้วที่มีการยืนยันว่าวิธีการที่ได้รับการเรียก
ดังนั้นตามที่ Sean Copenhaver อธิบายไว้ในคำตอบของเขาความแตกต่างคือ mocks สร้างความคาดหวัง (เช่นยืนยันว่าพวกเขาถูกเรียกหรือไม่)
ต้นขั้วไม่ล้มเหลวในการทดสอบของคุณเยาะเย้ย
ฉันคิดว่าคำตอบที่ง่ายและชัดเจนที่สุดเกี่ยวกับคำถามนี้ได้รับจากRoy Osheroveในหนังสือของเขาศิลปะการทดสอบหน่วย (หน้า 85)
วิธีที่ง่ายที่สุดที่จะบอกว่าเราติดต่อกับต้นขั้วก็คือการสังเกตว่าต้นขั้วนั้นไม่เคยล้มเหลวในการทดสอบ การยืนยันการทดสอบใช้เสมอกับคลาสที่อยู่ภายใต้การทดสอบ
ในทางกลับกันการทดสอบจะใช้วัตถุจำลองเพื่อตรวจสอบว่าการทดสอบล้มเหลวหรือไม่ [ ... ]
อีกครั้งวัตถุจำลองเป็นวัตถุที่เราใช้เพื่อดูว่าการทดสอบล้มเหลวหรือไม่
นั่นหมายความว่าหากคุณทำการยืนยันกับของปลอมหมายความว่าคุณใช้ของปลอมเป็นล้อเลียนถ้าคุณใช้ของปลอมเท่านั้นเพื่อทำการทดสอบโดยไม่ยืนยันว่าคุณใช้ปลอมเป็นต้นขั้ว
อ่านคำอธิบายทั้งหมดข้างต้นขอผมลองย่อ:
การเยาะเย้ยเป็นเพียงการทดสอบพฤติกรรมการทำให้แน่ใจว่าวิธีการบางอย่างที่เรียกว่า Stub เป็นรุ่นทดสอบ (ต่อ se) ของวัตถุเฉพาะ
คุณหมายถึงวิธีการของ Apple?
หากคุณเปรียบเทียบกับการดีบัก:
Stubเป็นเหมือนการทำให้แน่ใจว่าวิธีการส่งกลับค่าที่ถูกต้อง
เยาะเย้ยเป็นจริงก้าวเข้าสู่วิธีการและทำให้แน่ใจว่าทุกอย่างภายในถูกต้องก่อนที่จะส่งกลับค่าที่ถูกต้อง
การใช้แบบจำลองทางจิตช่วยให้ฉันเข้าใจสิ่งนี้มากกว่าคำอธิบายและบทความทั้งหมดซึ่งไม่ได้ "จม"
ลองนึกภาพลูกของคุณมีจานแก้ววางอยู่บนโต๊ะและเขาก็เริ่มเล่นกับมัน ตอนนี้คุณกลัวว่ามันจะพัง ดังนั้นคุณให้เขาแผ่นพลาสติกแทน นั่นจะเป็นการเยาะเย้ย (พฤติกรรมเดียวกัน, อินเทอร์เฟซเดียวกัน, การใช้งาน "เบาลง")
ตอนนี้พูดว่าคุณไม่มีแผ่นพลาสติกทดแทนดังนั้นคุณจะอธิบายว่า "ถ้าคุณเล่นต่อไปมันจะพัง!" นั่นคือต้นขั้วคุณได้กำหนดสถานะไว้ล่วงหน้าแล้ว
Dummyจะแยกเขาไม่ได้ใช้ ... และSpyอาจจะมีบางสิ่งบางอย่างเช่นการให้คำอธิบายเดียวกับที่คุณใช้อยู่แล้วที่ทำงาน
ฉันคิดว่าความแตกต่างที่สำคัญที่สุดระหว่างพวกเขาคือความตั้งใจของพวกเขา
ให้ฉันพยายามอธิบายในWHY ต้นขั้วและทำไมล้อเลียน
สมมติว่าฉันกำลังเขียนโค้ดทดสอบสำหรับคอนโทรลเลอร์ไทม์ไลน์สาธารณะของไคลเอ็นต์ Twitter ของ Mac
นี่คือตัวอย่างรหัสทดสอบ
twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline
โดยการเขียนจำลองคุณจะค้นพบความสัมพันธ์การทำงานร่วมกันของวัตถุโดยการตรวจสอบความคาดหวังที่จะพบในขณะที่ต้นขั้วเพียงจำลองพฤติกรรมของวัตถุ
ฉันขอแนะนำให้อ่านบทความนี้หากคุณพยายามที่จะรู้เพิ่มเติมเกี่ยวกับ mocks: http://jmock.org/oopsla2004.pdf
เพื่อให้ชัดเจนและใช้งานได้จริง:
Stub: คลาสหรือวัตถุที่ใช้วิธีการของคลาส / วัตถุที่จะแกล้งและส่งคืนสิ่งที่คุณต้องการเสมอ
ตัวอย่างใน JavaScript:
var Stub = {
method_a: function(param_a, param_b){
return 'This is an static result';
}
}
เยาะเย้ย: stub เดียวกัน แต่เพิ่มตรรกะบางอย่างที่ "ตรวจสอบ" เมื่อมีการเรียกวิธีการเพื่อให้คุณมั่นใจได้ว่าการใช้งานบางอย่างเรียกวิธีการนั้น
@mLevan พูดว่าจินตนาการเป็นตัวอย่างที่คุณกำลังทดสอบคลาสการลงทะเบียนผู้ใช้ หลังจากเรียกบันทึกแล้วควรเรียกใช้ SendConfirmationEmail
รหัสโง่มากตัวอย่าง:
var Mock = {
calls: {
method_a: 0
}
method_a: function(param_a, param_b){
this.method_a++;
console.log('Mock.method_a its been called!');
}
}
สไลด์นี้อธิบายความแตกต่างหลัก ๆ ดีมาก
* จาก CSE 403 บทที่ 16 มหาวิทยาลัยวอชิงตัน (สไลด์ที่สร้างโดย "Marty Stepp")
ให้ดูการทดสอบคู่ผสม:
Stub : Stub เป็นวัตถุที่เก็บข้อมูลที่กำหนดไว้ล่วงหน้าและใช้เพื่อรับสายระหว่างการทดสอบ เช่น : วัตถุที่ต้องการดึงข้อมูลบางส่วนจากฐานข้อมูลเพื่อตอบสนองต่อการเรียกใช้เมธอด
Mocks : Mocks เป็นวัตถุที่ลงทะเบียนการโทรที่พวกเขาได้รับ ในการยืนยันการทดสอบเราสามารถตรวจสอบ Mocks ว่าการกระทำที่คาดหวังทั้งหมดได้ดำเนินการ เช่น : ฟังก์ชันที่เรียกใช้บริการส่งอีเมล นานเพียงตรวจสอบนี้
ปลอมเป็นคำทั่วไปที่สามารถนำมาใช้เพื่ออธิบายทั้งต้นขั้วหรือวัตถุจำลอง (เขียนด้วยลายมือหรืออื่น ๆ ) เพราะพวกเขาทั้งสองมีลักษณะเหมือนวัตถุจริง
ไม่ว่าของปลอมจะเป็นต้นขั้วหรือจำลองขึ้นอยู่กับว่ามันถูกใช้ในการทดสอบปัจจุบันหรือไม่ ถ้ามันถูกใช้เพื่อตรวจสอบการโต้ตอบ (ยืนยันกับ) มันเป็นวัตถุจำลอง มิฉะนั้นจะเป็นต้นขั้ว
การปลอมทำให้การทดสอบดำเนินไปอย่างราบรื่น หมายความว่าผู้อ่านการทดสอบในอนาคตของคุณจะเข้าใจสิ่งที่จะเป็นพฤติกรรมของวัตถุปลอมโดยไม่จำเป็นต้องอ่านซอร์สโค้ดของมัน (โดยไม่จำเป็นต้องพึ่งพาทรัพยากรภายนอก)
การทดสอบการทำงานราบรื่นหมายถึงอะไร
ตัวอย่าง Forex ในรหัสด้านล่าง:
public void Analyze(string filename)
{
if(filename.Length<8)
{
try
{
errorService.LogError("long file entered named:" + filename);
}
catch (Exception e)
{
mailService.SendEMail("admin@hotmail.com", "ErrorOnWebService", "someerror");
}
}
}
คุณต้องการทดสอบเมธอด mailService.SendEMail ()เพื่อที่คุณจะต้องทำการจำลองข้อยกเว้นในการทดสอบดังนั้นคุณเพียงแค่สร้างคลาส ErrorService ปลอมของสตับเพื่อจำลองผลลัพธ์นั้นจากนั้นโค้ดทดสอบของคุณจะสามารถทดสอบได้ เมธอด mailService.SendEMail () ตามที่คุณเห็นคุณจำเป็นต้องจำลองผลลัพธ์ซึ่งมาจากคลาส ErrorService ภายนอกการพึ่งพาอื่น
ขวาจากMock Rolesกระดาษไม่ใช่ Objectโดยผู้พัฒนา jMock:
Stub คือการใช้งานจริงของรหัสการผลิตที่ส่งคืนผลลัพธ์กระป๋อง วัตถุจำลองทำหน้าที่เหมือนสตับ แต่ยังรวมถึงการยืนยันถึงการโต้ตอบของวัตถุเป้าหมายกับเพื่อนบ้าน
ดังนั้นความแตกต่างที่สำคัญคือ:
เพื่อสรุปผลในขณะที่ยังพยายามที่จะแยกย้ายกันความสับสนจากบทความของพรานล่าสัตว์ชื่อเรื่อง: mocks มีความไม่สมบูรณ์ แต่พวกเขาจะไม่เพียง แต่ไม่สมบูรณ์
ฉันกำลังอ่านThe Art of Unit Testingและสะดุดกับคำจำกัดความต่อไปนี้:
ปลอมเป็นคำทั่วไปที่สามารถนำมาใช้เพื่ออธิบายทั้งต้นขั้วหรือวัตถุจำลอง (เขียนด้วยลายมือหรืออื่น ๆ ) เพราะพวกเขาทั้งสองมีลักษณะเหมือนวัตถุจริง ไม่ว่าของปลอมจะเป็นต้นขั้วหรือจำลองขึ้นอยู่กับว่ามันถูกใช้ในการทดสอบปัจจุบันหรือไม่ ถ้ามันใช้ในการตรวจสอบการปฏิสัมพันธ์ (ถูกกล่าวหาผิด) มันเป็นวัตถุจำลอง มิฉะนั้นจะเป็นต้นขั้ว
ฉันมาข้ามบทความที่น่าสนใจนี้โดย UncleBob The Little เยาะเย้ย มันอธิบายคำศัพท์ทั้งหมดในลักษณะที่เข้าใจง่ายดังนั้นจึงมีประโยชน์สำหรับผู้เริ่มต้น บทความ Martin Fowlers อ่านยากโดยเฉพาะสำหรับผู้เริ่มต้นอย่างฉัน
Stubช่วยให้เราสามารถทำการทดสอบ อย่างไร? มันให้ค่าที่ช่วยในการรันการทดสอบ ค่าเหล่านี้ไม่ได้เกิดขึ้นจริงและเราสร้างค่าเหล่านี้เพื่อทำการทดสอบ ตัวอย่างเช่นเราสร้าง HashMap เพื่อให้คุณค่ากับเราซึ่งคล้ายกับค่าในตารางฐานข้อมูล ดังนั้นแทนที่จะโต้ตอบกับฐานข้อมูลโดยตรงเราจึงโต้ตอบกับ Hashmap
จำลองเป็นวัตถุปลอมที่รันการทดสอบ ที่เราใส่ยืนยัน
ดูตัวอย่างด้านล่างของ mocks vs stubs โดยใช้ C # และ Moq framework Moq ไม่มีคำหลักพิเศษสำหรับ Stub แต่คุณสามารถใช้ Mock object เพื่อสร้าง stub ได้เช่นกัน
namespace UnitTestProject2
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class UnitTest1
{
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
}
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(0);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
}
/// <summary>
/// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
{
// Arrange
var stubEntityRepository = new Mock<IEntityRepository>();
stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
.Returns("Stub");
const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
var entity = new EntityClass(stubEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
}
}
public class EntityClass
{
private IEntityRepository _entityRepository;
public EntityClass(IEntityRepository entityRepository)
{
this._entityRepository = entityRepository;
}
public string Name { get; set; }
public string GetNameWithPrefix(int id)
{
string name = string.Empty;
if (id > 0)
{
name = this._entityRepository.GetName(id);
}
return "Mr. " + name;
}
}
public interface IEntityRepository
{
string GetName(int id);
}
public class EntityRepository:IEntityRepository
{
public string GetName(int id)
{
// Code to connect to DB and get name based on Id
return "NameFromDb";
}
}
}
มุมมองการทดสอบ Stub และ Mock:
Stubคือการนำดัมมี่ไปใช้โดยผู้ใช้ด้วยวิธีสแตติกหมายถึงใน Stub ที่เขียนโค้ดการใช้งาน ดังนั้นจึงไม่สามารถจัดการคำจำกัดความของบริการและเงื่อนไขแบบไดนามิกได้โดยปกติจะทำในกรอบงาน JUnit โดยไม่ต้องใช้กรอบการเยาะเย้ย
จำลองนั้นยังใช้งานแบบจำลองได้ แต่การนำไปใช้งานทำได้อย่างไดนามิกโดยใช้ Mocking frameworks เช่น Mockito ดังนั้นเราสามารถจัดการเงื่อนไขและคำจำกัดความการบริการเป็นวิธีแบบไดนามิกเช่น mocks สามารถสร้างแบบไดนามิกจากรหัสที่รันไทม์ ดังนั้นการใช้การเยาะเย้ยเราสามารถใช้ Stubs แบบไดนามิก
บวกกับคำตอบที่เป็นประโยชน์หนึ่งในจุดที่มีประสิทธิภาพที่สุดในการใช้ Mocks กว่า Subs
ถ้าทำงานร่วมกัน [ซึ่งรหัสหลักขึ้นอยู่กับมัน] ไม่อยู่ภายใต้การควบคุมของเรา (เช่นจากห้องสมุดของบุคคลที่สาม)
ในกรณีนี้ต้นขั้วเป็นเรื่องยากมากที่จะเขียนมากกว่าการเยาะเย้ย
ฉันใช้ตัวอย่างไพ ธ อนในคำตอบเพื่อแสดงความแตกต่าง
Stub - Stubbing เป็นเทคนิคการพัฒนาซอฟต์แวร์ที่ใช้ในการใช้วิธีการเรียนในช่วงต้นของวงจรการพัฒนา โดยทั่วไปจะใช้เป็นตัวยึดตำแหน่งสำหรับการใช้งานอินเทอร์เฟซที่รู้จักซึ่งอินเตอร์เฟสจะถูกสรุปหรือรู้จัก แต่การนำไปใช้ยังไม่เป็นที่รู้จัก คุณเริ่มต้นด้วย stubs ซึ่งหมายความว่าคุณเพียงเขียนคำจำกัดความของฟังก์ชั่นลงและปล่อยให้รหัสจริงในภายหลัง ข้อได้เปรียบคือคุณจะไม่ลืมวิธีการและคุณสามารถคิดเกี่ยวกับการออกแบบของคุณในขณะที่เห็นในรหัส คุณสามารถให้ stub ของคุณคืนการตอบสนองแบบสแตติกเพื่อให้สามารถใช้การตอบกลับโดยส่วนอื่น ๆ ของรหัสของคุณได้ทันที วัตถุ Stub ให้การตอบสนองที่ถูกต้อง แต่มันคงที่ไม่ว่าคุณจะป้อนข้อมูลใดก็ตามคุณจะได้รับการตอบกลับเหมือนเดิมเสมอ:
class Foo(object):
def bar1(self):
pass
def bar2(self):
#or ...
raise NotImplementedError
def bar3(self):
#or return dummy data
return "Dummy Data"
วัตถุจำลองถูกนำมาใช้ในกรณีทดสอบจำลองซึ่งตรวจสอบความถูกต้องของวิธีการบางอย่างบนวัตถุเหล่านั้น วัตถุจำลองเป็นวัตถุจำลองที่เลียนแบบพฤติกรรมของวัตถุจริงด้วยวิธีการควบคุม โดยทั่วไปคุณสร้างวัตถุจำลองเพื่อทดสอบพฤติกรรมของวัตถุอื่น ๆ Mocks ให้เราจำลองทรัพยากรที่ไม่พร้อมใช้งานหรือไม่สะดวกสำหรับการทดสอบหน่วย
mymodule.py:
import os
import os.path
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
test.py:
from mymodule import rm
import mock
import unittest
class RmTestCase(unittest.TestCase):
@mock.patch('mymodule.os')
def test_rm(self, mock_os):
rm("any path")
# test that rm called os.remove with the right parameters
mock_os.remove.assert_called_with("any path")
if __name__ == '__main__':
unittest.main()
นี่เป็นตัวอย่างพื้นฐานที่เพิ่งรัน rm และยืนยันพารามิเตอร์ที่ถูกเรียกด้วย คุณสามารถใช้การเยาะเย้ยกับวัตถุไม่ใช่แค่ฟังก์ชั่นดังที่แสดงไว้ที่นี่และคุณยังสามารถคืนค่าเพื่อให้วัตถุจำลองสามารถใช้แทนต้นขั้วสำหรับการทดสอบได้
เพิ่มเติมเกี่ยวกับunittest.mockหมายเหตุใน python 2.x mock ไม่ได้รวมอยู่ใน unittest แต่เป็นโมดูลที่สามารถดาวน์โหลดได้ซึ่งสามารถดาวน์โหลดผ่าน pip (mock pip install mock)
ฉันยังได้อ่าน "ศิลปะแห่งการทดสอบหน่วย" โดย Roy Osherove และฉันคิดว่ามันจะดีถ้าหนังสือที่คล้ายกันเขียนขึ้นโดยใช้ตัวอย่าง Python และ Python หากใครรู้หนังสือเล่มนี้โปรดแชร์ ไชโย :)
ต้นขั้วเป็นวัตถุปลอมสร้างขึ้นเพื่อวัตถุประสงค์ในการทดสอบ mock คือ stub ที่บันทึกว่าการโทรที่คาดไว้เกิดขึ้นอย่างมีประสิทธิภาพหรือไม่
Stub เป็นฟังก์ชันว่างซึ่งใช้เพื่อหลีกเลี่ยงข้อยกเว้นที่ไม่สามารถจัดการได้ในระหว่างการทดสอบ:
function foo(){}
จำลองเป็นฟังก์ชั่นประดิษฐ์ที่ใช้เพื่อหลีกเลี่ยงระบบปฏิบัติการสภาพแวดล้อมหรือการพึ่งพาฮาร์ดแวร์ในระหว่างการทดสอบ:
function foo(bar){ window = this; return window.toString(bar); }
ในแง่ของการยืนยันและรัฐ:
อ้างอิง
มีคำตอบที่ถูกต้องมากมาย แต่ฉันคิดว่าควรพูดถึงลุงของฟอร์มนี้: https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
คำอธิบายที่ดีที่สุดที่เคยมีตัวอย่าง!
การเยาะเย้ยเป็นทั้งด้านเทคนิคและวัตถุที่ใช้งานได้
จำลองเป็นทางด้านเทคนิค มันถูกสร้างขึ้นแน่นอนโดยห้องสมุดเยาะเย้ย (EasyMock, JMockit และเมื่อเร็ว ๆ Mockito เป็นที่รู้จักสำหรับเหล่านี้) ขอบคุณที่สร้างรหัสไบต์
การใช้งานจำลองถูกสร้างขึ้นในวิธีที่เราสามารถใช้มันในการส่งกลับค่าที่เฉพาะเจาะจงเมื่อมีการเรียกวิธีการ แต่ยังมีสิ่งอื่น ๆ เช่นการตรวจสอบว่าวิธีการเยาะเย้ยถูกเรียกด้วยพารามิเตอร์เฉพาะบางอย่าง ไม่มีการตรวจสอบอย่างเข้มงวด)
ยกตัวอย่างเยาะเย้ย:
@Mock Foo fooMock
การบันทึกพฤติกรรม:
when(fooMock.hello()).thenReturn("hello you!");
การตรวจสอบการร้องขอ:
verify(fooMock).hello()
เห็นได้ชัดว่าไม่ใช่วิธีที่เป็นธรรมชาติในการสร้างอินสแตนซ์ / แทนที่คลาส / พฤติกรรมของฟู นั่นเป็นเหตุผลที่ฉันอ้างถึงด้านเทคนิค
แต่การเยาะเย้ยนั้นก็ใช้งานได้เพราะมันเป็นตัวอย่างของคลาสที่เราต้องแยกออกจาก SUT และด้วยพฤติกรรมที่บันทึกไว้ในนั้นเราสามารถใช้มันใน SUT ด้วยวิธีเดียวกันกับที่เราจะทำกับต้นขั้ว
stub เป็นเพียงวัตถุที่ใช้งานได้: นั่นเป็นตัวอย่างของคลาสที่เราต้องแยกออกจาก SUT และนั่นคือทั้งหมด นั่นหมายถึงว่าต้องมีการกำหนดทั้งส่วนต้นขั้วและพฤติกรรมทั้งหมดที่จำเป็นในระหว่างการทดสอบหน่วยของเราอย่างชัดเจน
ตัวอย่างเช่น stub hello()
จะต้อง subclass Foo
class (หรือใช้ส่วนต่อประสานกับมัน) และแทนที่hello()
:
public class HelloStub extends Hello{
public String hello {
return "hello you!";
}
}
หากสถานการณ์จำลองการทดสอบอื่นต้องการผลตอบแทนเป็นค่าอื่นเราอาจต้องกำหนดวิธีทั่วไปในการตั้งค่าผลตอบแทน:
public class HelloStub extends Hello{
public HelloStub(String helloReturn){
this.helloReturn = helloReturn;
}
public String hello {
return helloReturn;
}
}
สถานการณ์อื่น ๆ : ถ้าฉันมีวิธีผลข้างเคียง (ไม่กลับมา) และฉันจะตรวจสอบว่าวิธีการที่ถูกเรียกฉันอาจจะต้องเพิ่มบูลีนหรือเคาน์เตอร์ในชั้นต้นขั้วเพื่อนับจำนวนวิธีที่ถูกเรียก
ข้อสรุป
Stub ต้องการค่าใช้จ่าย / รหัสจำนวนมากในการเขียนเพื่อทดสอบหน่วยของคุณ สิ่งที่เยาะเย้ยป้องกันไม่ให้ขอบคุณที่ให้การบันทึก / ตรวจสอบคุณสมบัติออกจากกล่อง
นั่นเป็นเหตุผลที่ทุกวันนี้วิธีการสตับนั้นไม่ค่อยได้ใช้ในทางปฏิบัติกับการมีห้องสมุดจำลองที่ยอดเยี่ยม
เกี่ยวกับบทความ Martin Fowler: ฉันไม่คิดว่าจะเป็นโปรแกรมเมอร์ "ผู้เยาะเย้ย" ในขณะที่ฉันใช้ mocks และฉันหลีกเลี่ยงที่ไม่สมบูรณ์
แต่ฉันใช้จำลองเมื่อจำเป็นจริงๆ (การอ้างอิงที่น่ารำคาญ) และฉันชอบการทดสอบการตัดและการทดสอบการรวมขนาดเล็กเมื่อฉันทดสอบคลาสที่มีการอ้างอิงซึ่งการเยาะเย้ยจะเป็นค่าใช้จ่าย