หน่วยทดสอบคลาสที่ใช้ DI โดยไม่ต้องทดสอบภายใน


12

ฉันมีชั้นเรียนซึ่งได้รับการปรับโครงสร้างใหม่ใน 1 ชั้นหลักและชั้นเรียนขนาดเล็ก 2 ชั้น คลาสหลักใช้ฐานข้อมูล (เหมือนคลาสของฉัน) และส่งอีเมล ดังนั้นคลาสหลักจะมีIPersonRepositoryและแบบIEmailRepositoryที่เขาส่งไปยังคลาสที่เล็กกว่า 2 คลาส

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

แต่เป็นชั้นใช้IPersonRepositoryและIEmailRepositoryผมมีการระบุ (จำลอง / จำลอง) IPersonRepositoryผลการค้นหาสำหรับวิธีการบางอย่างสำหรับ คลาสหลักจะคำนวณข้อมูลบางส่วนตามข้อมูลที่มีอยู่และส่งคืนข้อมูลนั้น ถ้าฉันต้องการทดสอบสิ่งนั้นฉันไม่เห็นว่าฉันจะเขียนแบบทดสอบได้อย่างไรโดยไม่ระบุว่าIPersonRepository.GetSavingsByCustomerIdผลตอบแทน x แต่จากนั้นการทดสอบหน่วยของฉัน 'รู้' เกี่ยวกับงานภายในเพราะ 'รู้' ว่าวิธีไหนที่จะเยาะเย้ยและไม่ทำ

ฉันจะทดสอบคลาสที่ฉีดการพึ่งพาโดยไม่ต้องทดสอบรู้เกี่ยวกับ internals ได้อย่างไร

พื้นหลัง:

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

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


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

คำตอบ:


7

คลาสหลักจะคำนวณข้อมูลบางส่วนตามข้อมูลที่มีอยู่และส่งคืนข้อมูลนั้น ถ้าฉันต้องการทดสอบสิ่งนั้นฉันไม่เห็นว่าฉันจะเขียนแบบทดสอบได้อย่างไรโดยไม่ระบุว่าIPersonRepository.GetSavingsByCustomerIdผลตอบแทน x แต่จากนั้นการทดสอบหน่วยของฉัน 'รู้' เกี่ยวกับงานภายในเพราะ 'รู้' ว่าวิธีไหนที่จะเยาะเย้ยและไม่ทำ

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

ฉันจะทดสอบคลาสที่ฉีดการพึ่งพาโดยไม่ต้องทดสอบรู้เกี่ยวกับ internals ได้อย่างไร

มีวิธีแก้ไขสองวิธีที่คุณสามารถนำมาใช้เพื่อแก้ไขปัญหาการละเมิดนี้:

1)IPersonRepositoryจัดให้มีการเยาะเย้ยที่สมบูรณ์ของ วิธีที่คุณอธิบายไว้ในปัจจุบันคือการจำลองคู่กับการทำงานภายในของวิธีการทดสอบโดยการเยาะเย้ยวิธีการที่จะเรียกเท่านั้น หากคุณจัดหา mocks สำหรับวิธีการทั้งหมดของIPersonRepositoryแล้วคุณลบการมีเพศสัมพันธ์นั้น การทำงานด้านในสามารถเปลี่ยนแปลงได้โดยไม่กระทบต่อการเยาะเย้ยทำให้การทดสอบมีความเปราะน้อยลง

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

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

ข้อดีของวิธีนี้คือหลีกเลี่ยงความต้องการ mocks (นอกเหนือจากวิธีทดสอบที่คุณฉีดเข้าไปในวิธีทดสอบ) ข้อเสียคือมันสามารถทำให้ลายเซ็นของวิธีการเปลี่ยนแปลงในการตอบสนองต่อความต้องการของวิธีการเปลี่ยนแปลง

ทั้งสองวิธีมีข้อดีข้อเสียดังนั้นเลือกวิธีที่เหมาะสมกับความต้องการของคุณที่สุด


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

1

My Approach คือการสร้างเวอร์ชั่น 'จำลอง' ของที่เก็บซึ่งอ่านจากไฟล์ง่าย ๆ ที่มีข้อมูลที่ต้องการ

ซึ่งหมายความว่าการทดสอบแต่ละรายการไม่ 'รู้' เกี่ยวกับการตั้งค่าจำลอง แต่เห็นได้ชัดว่าโครงการทดสอบโดยรวมจะอ้างอิงการจำลองและมีไฟล์ติดตั้งเป็นต้น

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

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


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

1
แก้ไขเพื่ออธิบายว่าทำไมวิธีการของฉันจะดีกว่าแม้จะยังไม่สมบูรณ์แบบสำหรับสถานการณ์ฉันคิดว่าคุณหมายถึง
Ewan

1

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

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

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