ควรเยาะเย้ยเมื่อใด


143

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


ฉันขอแนะนำให้เยาะเย้ยการอ้างอิงที่ไม่อยู่ในกระบวนการและเฉพาะการโต้ตอบที่สามารถสังเกตได้จากภายนอก (เซิร์ฟเวอร์ SMTP บัสข้อความ ฯลฯ ) อย่าล้อเลียนฐานข้อมูลมันเป็นรายละเอียดการใช้งาน เพิ่มเติมได้ที่นี่: enterprisecraftsmanship.com/posts/when-to-mock
Vladimir

คำตอบ:


125

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

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

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

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

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

TL; DR: จำลองการพึ่งพาการทดสอบหน่วยของคุณทุกครั้งที่สัมผัส


174
คำตอบนี้รุนแรงเกินไป การทดสอบหน่วยสามารถทำได้และควรใช้มากกว่าวิธีเดียวตราบเท่าที่ทั้งหมดเป็นหน่วยเดียวกัน การทำอย่างอื่นจะต้องมีการล้อเลียน / แกล้งทำมากเกินไปซึ่งนำไปสู่การทดสอบที่ซับซ้อนและเปราะบาง เฉพาะการอ้างอิงที่ไม่ได้เป็นของหน่วยที่อยู่ระหว่างการทดสอบเท่านั้นที่ควรถูกแทนที่ด้วยการล้อเลียน
Rogério

10
คำตอบนี้ยังมองโลกในแง่ดีเกินไป มันจะดีกว่าถ้ามันรวมเอาข้อบกพร่องของวัตถุจำลองของ @ Jan เข้าไว้ด้วยกัน
Jeff Axelrod

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

2
จำลองการพึ่งพาการทดสอบหน่วยของคุณทุกครั้งที่สัมผัส สิ่งนี้อธิบายทุกอย่าง
Teoman shipahi

2
TL; DR: จำลองการพึ่งพาการทดสอบหน่วยของคุณทุกครั้งที่สัมผัส - นี่ไม่ใช่วิธีการที่ยอดเยี่ยมจริงๆม็อกกิโตกล่าว - อย่าล้อเลียนทุกอย่าง (downvoted)
p_champ

170

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

ตัวอย่างเช่นเราต้องการทดสอบเมธอดนั้นsendInvitations(MailServer mailServer)เรียกMailServer.createMessage()เพียงครั้งเดียวและเรียกMailServer.sendMessage(m)ครั้งเดียวเหมือนกันและจะไม่มีการเรียกวิธีอื่นบนMailServerอินเทอร์เฟซ นี่คือเวลาที่เราสามารถใช้วัตถุจำลอง

ด้วยวัตถุจำลองแทนที่จะผ่านของจริงMailServerImplหรือการทดสอบTestMailServerเราสามารถผ่านการจำลองการใช้งานMailServerอินเทอร์เฟซได้ ก่อนที่เราจะผ่านการล้อเลียนMailServerเราจะ "ฝึก" มันเพื่อให้รู้ว่าวิธีใดที่เรียกร้องให้คาดหวังและสิ่งที่จะคืนค่ากลับ ในตอนท้ายวัตถุจำลองยืนยันว่าเมธอดที่คาดหวังทั้งหมดถูกเรียกตามที่คาดไว้

ฟังดูดีในทางทฤษฎี แต่ก็มีข้อเสียบางประการเช่นกัน

ข้อบกพร่องของการเยาะเย้ย

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

นี่คือตัวอย่างใน pseudocode สมมติว่าเราได้สร้างMySorterคลาสขึ้นมาแล้วและเราต้องการทดสอบ:

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(ในตัวอย่างนี้เราถือว่าไม่ใช่อัลกอริทึมการเรียงลำดับเฉพาะเช่นการเรียงลำดับด่วนที่เราต้องการทดสอบในกรณีนี้การทดสอบครั้งหลังจะใช้ได้จริง)

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

ล้อเลียนเป็นต้นขั้ว

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

สมมติว่าเรามีวิธีการsendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)ที่เราต้องการทดสอบ PdfFormatterวัตถุสามารถนำมาใช้ในการสร้างคำเชิญ นี่คือการทดสอบ:

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

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

แต่จะเกิดอะไรขึ้นในภายหลังเมื่อเราเปลี่ยนsendInvitations()หรือคลาสอื่น ๆ ที่sendInvitations()ใช้เพื่อสร้างไฟล์ PDF ที่สวยงามมากขึ้น? การทดสอบของเราล้มเหลวอย่างกะทันหันเพราะตอนนี้มีการPdfFormatterเรียกวิธีการมากขึ้นและเราไม่ได้ฝึกต้นขั้วให้คาดหวัง และโดยปกติแล้วไม่ใช่แค่การทดสอบเดียวที่ล้มเหลวในสถานการณ์เช่นนี้ แต่เป็นการทดสอบใด ๆ ที่เกิดขึ้นกับการใช้sendInvitations()วิธีนี้ไม่ว่าทางตรงหรือทางอ้อม เราต้องแก้ไขการทดสอบเหล่านั้นทั้งหมดโดยเพิ่มการฝึกอบรมให้มากขึ้น โปรดสังเกตด้วยว่าเราไม่สามารถลบวิธีการที่ไม่จำเป็นได้อีกต่อไปเพราะเราไม่รู้ว่าวิธีใดที่ไม่จำเป็น อีกครั้งเป็นอุปสรรคต่อการปรับโครงสร้างใหม่

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

จะแก้ไขได้อย่างไร? ได้อย่างง่ายดาย:

  • ลองใช้ชั้นเรียนจริงแทนการล้อเลียนทุกครั้งที่ทำได้ ใช้ของจริงPdfFormatterImpl. หากไม่สามารถทำได้ให้เปลี่ยนคลาสจริงเพื่อให้เป็นไปได้ การไม่สามารถใช้ชั้นเรียนในการทดสอบมักชี้ให้เห็นปัญหาบางอย่างกับชั้นเรียน การแก้ไขปัญหาเป็นสถานการณ์ที่ชนะ - คุณแก้ไขคลาสแล้วและคุณมีการทดสอบที่ง่ายกว่า ในทางกลับกันการไม่แก้ไขและการใช้ล้อเลียนเป็นสถานการณ์ที่ไม่มีทางชนะคุณไม่ได้แก้ไขคลาสจริงและคุณมีการทดสอบที่ซับซ้อนและอ่านได้น้อยกว่าซึ่งขัดขวางการปรับแต่งเพิ่มเติม
  • ลองสร้างการใช้งานอินเทอร์เฟซแบบทดสอบอย่างง่ายแทนที่จะล้อเลียนในการทดสอบแต่ละครั้งและใช้คลาสทดสอบนี้ในการทดสอบทั้งหมดของคุณ สร้างTestPdfFormatterที่ไม่ทำอะไรเลย ด้วยวิธีนี้คุณสามารถเปลี่ยนได้เพียงครั้งเดียวสำหรับการทดสอบทั้งหมดและการทดสอบของคุณจะไม่เกะกะกับการตั้งค่าที่ยาวนานซึ่งคุณจะต้องฝึกต้นขั้วของคุณ

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

สำหรับรายละเอียดบางอย่างเพิ่มเติมเกี่ยวกับข้อบกพร่องของ mocks เห็นวัตถุจำลอง: บกพร่องและกรณีการใช้งาน


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

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

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

7
"การไม่สามารถใช้ชั้นเรียนในการทดสอบมักชี้ให้เห็นปัญหาบางอย่างกับชั้นเรียน" ถ้าชั้นเรียนเป็นบริการ (เช่นการเข้าถึงฐานข้อมูลหรือพร็อกซีไปยังบริการเว็บ) ควรถือว่าเป็นการ
พึ่งพา

1
แต่จะเกิดอะไรขึ้นในภายหลังเมื่อเราเปลี่ยน sendInvitations ()? หากรหัสที่อยู่ระหว่างการทดสอบได้รับการแก้ไขจะไม่รับประกันสัญญาก่อนหน้านี้อีกต่อไปดังนั้นจึงต้องล้มเหลว และมักจะไม่ได้เป็นเพียงหนึ่งในการทดสอบที่ล้มเหลวในสถานการณ์เช่นนี้ หากเป็นกรณีนี้แสดงว่าโค้ดไม่ถูกนำไปใช้อย่างสะอาด การตรวจสอบวิธีการเรียกใช้การอ้างอิงควรทดสอบเพียงครั้งเดียว (ในการทดสอบหน่วยที่เหมาะสม) คลาสอื่น ๆ ทั้งหมดจะใช้อินสแตนซ์จำลองเท่านั้น ดังนั้นฉันจึงไม่เห็นประโยชน์ใด ๆ ที่ผสมผสานการรวมเข้ากับการทดสอบหน่วย
Christopher Will

55

หลักการง่ายๆ:

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


5

คุณควรเยาะเย้ยวัตถุเมื่อคุณมีการพึ่งพาในหน่วยของรหัสที่คุณพยายามทดสอบว่าต้องเป็น "อย่างนั้น"

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

คุณสามารถดูพอดคาสต์ที่ยอดเยี่ยมในหัวข้อนี้ได้ที่นี่


2
ตอนนี้ลิงก์จะนำทางไปยังตอนปัจจุบันไม่ใช่ตอนที่ต้องการ พอดคาสต์ที่ตั้งใจไว้คือhanselminutes.com/32/mock-objectsหรือไม่
C Perkins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.