TDD: เยาะเย้ยวัตถุที่อยู่ติดกันอย่างแน่นหนา


10

บางครั้งวัตถุก็จำเป็นต้องมีการรวมกันอย่างแน่นหนา ตัวอย่างเช่นCsvFileคลาสอาจต้องทำงานอย่างแน่นหนากับCsvRecordคลาส (หรือICsvRecordอินเทอร์เฟซ)

อย่างไรก็ตามจากสิ่งที่ฉันได้เรียนรู้ในอดีตหนึ่งในหลักการสำคัญของการพัฒนาที่ขับเคลื่อนด้วยการทดสอบคือ "อย่าทดสอบมากกว่าหนึ่งคลาสในเวลาเดียวกัน" หมายความว่าคุณควรใช้ICsvRecordmocks CsvRecordหรือไม่สมบูรณ์มากกว่ากรณีที่แท้จริงของ

อย่างไรก็ตามหลังจากลองใช้วิธีนี้ฉันสังเกตว่าการเยาะเย้ยในCsvRecordชั้นเรียนอาจทำให้ขนดกเล็กน้อย ซึ่งทำให้ฉันเป็นหนึ่งในสองข้อสรุป:

  1. ยากที่จะเขียนการทดสอบหน่วย! นั่นคือกลิ่นรหัส! ปรับปรุงโครงสร้าง!
  2. การเย้ยหยันการพึ่งพาอาศัยกันทุกครั้งนั้นไม่สมเหตุสมผล

เมื่อฉันแทนที่ mocks ของฉันด้วยCsvRecordอินสแตนซ์ที่เกิดขึ้นจริงสิ่งต่าง ๆ เป็นไปอย่างราบรื่นมากขึ้น เมื่อมองไปรอบ ๆ สำหรับความคิดของคนอื่นฉันก็สะดุดกับโพสต์บล็อกนี้ซึ่งดูเหมือนจะสนับสนุน # 2 ข้างต้น สำหรับวัตถุที่อยู่คู่กันอย่างเป็นธรรมชาติเราไม่ควรกังวลกับการล้อเลียน

ฉันจะออกนอกเส้นทาง? มีข้อเสียข้อสันนิษฐาน # 2 ข้างต้นหรือไม่ ฉันควรจะคิดเกี่ยวกับการปรับโครงสร้างการออกแบบใหม่หรือไม่?


1
ฉันคิดว่ามันเป็นความเข้าใจผิดที่พบบ่อยว่า "หน่วย" ใน "การทดสอบหน่วย" จะต้องเป็นหนึ่งชั้น ฉันคิดว่าตัวอย่างของคุณแสดงกรณีที่มันอาจจะดีกว่าที่ทั้งสองชั้นเรียนในรูปแบบหนึ่งหน่วย แต่อย่าเข้าใจฉันผิดฉันเห็นด้วยกับคำตอบทั้งหมดของ Robert Harvey
Doc Brown

คำตอบ:


11

หากคุณต้องการการประสานงานระหว่างสองคลาสเหล่านั้นจริงๆให้เขียนCsvCoordinatorคลาสที่ห่อหุ้มสองคลาสของคุณและทดสอบ

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

ทดสอบCsvRecordก่อน ตรวจสอบให้แน่ใจว่าผ่านการทดสอบทั้งหมดแล้ว จากนั้นไปข้างหน้าและใช้CsvRecordกับCsvFileชั้นเรียนของคุณระหว่างการทดสอบ ใช้เป็น stub / mock ทดสอบก่อน; กรอกข้อมูลทดสอบที่เกี่ยวข้องส่งผ่านไปCsvFileและเขียนกรณีทดสอบของคุณกับเรื่องนั้น


1
ใช่ CsvRecord สามารถทดสอบได้อย่างอิสระที่สุด ปัญหาคือว่าหากมีบางสิ่งผิดปกติใน CsvRecord ก็จะทำให้การทดสอบ CsvData ล้มเหลว แต่ฉันไม่คิดว่าเป็นปัญหาสำคัญ
ฟิล

1
ฉันคิดว่าคุณต้องการที่จะเกิดขึ้น :)
Robert Harvey

1
@RobertHarvey: ในทางทฤษฎีแล้วมันอาจกลายเป็นปัญหาถ้า CsvRecord และ CsvFile กำลังกลายเป็นคลาสที่ค่อนข้างซับซ้อนและหากการทดสอบแบ่งสำหรับ CsvFile ตอนนี้คุณไม่รู้ทันทีว่าเป็นปัญหาใน CsvFile หรือ CsvRecord แต่ฉันคิดว่ามันเป็นกรณีสมมุติมากกว่า - ถ้าฉันมีภาระหน้าที่ในการเขียนโปรแกรมคลาสดังกล่าวสำหรับโปรแกรมในโลกแห่งความเป็นจริงฉันจะทำแบบที่คุณอธิบาย
Doc Brown

2
@ ฟิล: ถ้าCsvRecordหยุดพักก็เห็นได้ชัดว่าCsvDataล้มเหลว; แต่นี่ก็โอเคเพราะคุณทดสอบCsvRecordก่อนและถ้าล้มเหลวCsvFileการทดสอบของคุณก็ไม่มีความหมาย คุณยังคงสามารถแยกแยะความแตกต่างระหว่างความผิดพลาดในและในCsvRecord CsvFile
tdammers

5

เหตุผลสำหรับการทดสอบหนึ่งคลาสในแต่ละครั้งคือคุณไม่ต้องการให้การทดสอบในชั้นหนึ่งมีการพึ่งพาพฤติกรรมของคลาสที่สอง นั่นหมายความว่าหากการทดสอบ Class A ของคุณออกกำลังกายฟังก์ชันใด ๆ ของ Class B คุณควรเยาะเย้ย Class B เพื่อลบการพึ่งพาฟังก์ชันการทำงานเฉพาะภายใน Class B

คลาสอย่างCsvRecordที่ฉันคิดว่าเป็นส่วนใหญ่สำหรับการจัดเก็บข้อมูล - ไม่ใช่คลาสที่มีฟังก์ชันการใช้งานมากเกินไป นั่นคือมันอาจมี constructors, getters, setters แต่ไม่มีเมธอดที่มีตรรกะมากมาย แน่นอนฉันเดาที่นี่ - บางทีคุณอาจจะเขียนคลาสที่เรียกCsvRecordว่าการคำนวณที่ซับซ้อนมากมาย

แต่ถ้าCsvRecordไม่มีตรรกะที่แท้จริงของตัวเองไม่มีอะไรที่จะได้รับจากการเยาะเย้ยมัน นี้เป็นจริงเพียงคำพังเพยเก่า - "ทำไม่ได้ค่าจำลองวัตถุ"

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


+1 การทดสอบใด ๆ ที่ผลลัพธ์ขึ้นอยู่กับความถูกต้องของพฤติกรรมของวัตถุมากกว่าหนึ่งรายการคือการทดสอบการรวมระบบไม่ใช่การทดสอบหน่วย คุณต้องจำลองวัตถุเหล่านี้เพื่อทดสอบหน่วยจริง สิ่งนี้ใช้ไม่ได้กับออบเจ็กต์ที่ไม่มีพฤติกรรมจริงในพวกมันแม้ว่าจะมีเพียง getters และ setters เท่านั้น
guillaume31

1

หมายเลข # 2 ไม่เป็นไร สิ่งต่าง ๆ สามารถและควรมีการเชื่อมโยงอย่างแน่นหนาหากแนวคิดของพวกเขาเป็นคู่อย่างแน่นหนา สิ่งนี้ควรหายากและหลีกเลี่ยงโดยทั่วไป แต่ในตัวอย่างที่คุณให้มามันสมเหตุสมผล


0

"ควบคู่" เรียนร่วมกันขึ้นอยู่กับอีกคนหนึ่ง สิ่งนี้ไม่ควรเกิดขึ้นกับสิ่งที่คุณกำลังอธิบาย - CsvRecord ไม่ควรสนใจ CsvFile ที่มีอยู่จริงๆดังนั้นการพึ่งพาจึงไปทางเดียวเท่านั้น ไม่เป็นไรการมีเพศสัมพันธ์ไม่แน่น

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

ดังนั้นหน่วยทดสอบ CsvRecord สำหรับพฤติกรรมที่ต้องการ

จากนั้นใช้กรอบการเยาะเย้ย (Mockito เป็นเลิศ) เพื่อทดสอบว่าหน่วยของคุณมีปฏิสัมพันธ์กับวัตถุมันขึ้นอยู่กับอย่างถูกต้อง พฤติกรรมที่คุณต้องการทดสอบจริงๆ - คือ CsvFile จัดการ CsvRcords ตามที่คาดไว้ ผลงานภายในของ CvsRecord ไม่ควรสำคัญ - มันเป็นวิธีที่ CvsFile สื่อสารกับมัน

ในที่สุด TDD ไม่เพียง แต่เกี่ยวกับการทดสอบหน่วย แน่นอนคุณสามารถ (และควร) เริ่มต้นด้วยการทดสอบฟังก์ชั่นที่ดูพฤติกรรมการทำงานของการทำงานของส่วนประกอบขนาดใหญ่ของคุณ - นั่นคือเรื่องราวของผู้ใช้หรือสถานการณ์ของคุณ การทดสอบหน่วยของคุณตั้งค่าความคาดหวังและตรวจสอบชิ้นส่วนการทดสอบการทำงานทำเช่นเดียวกันสำหรับทั้งหมด


1
-1 การมีเพศสัมพันธ์อย่างแน่นหนาไม่ได้แปลว่าการพึ่งพาแบบวนรอบนั่นเป็นความเข้าใจผิด ในตัวอย่างCsvFile มีการเชื่อมโยงอย่างแน่นหนากับCsvRecord(แต่ไม่ใช่รอบอื่น ๆ ) OP ถามว่าเป็นความคิดที่ดีที่จะทดสอบCsvFileโดยการแยกมันออกจากCsvRecordผ่านICsvRecordไม่ใช่ในทางกลับกัน
Doc Brown

2
@DocBrown: การมีเพศสัมพันธ์แน่นหรือไม่เป็นเรื่องของCsvFileขึ้นอยู่กับการทำงานภายในของCsvRecordนั่นคือจำนวนของสมมติฐานที่ไฟล์มีเกี่ยวกับการบันทึก อินเทอร์เฟซช่วยเอกสารและบังคับใช้สมมติฐานดังกล่าว (หรือมากกว่าการไม่มีสมมติฐานอื่น ๆ ) แต่จำนวนของการแต่งงานยังคงเหมือนเดิมยกเว้นว่าด้วยอินเทอร์เฟซคุณสามารถขอบันทึกคลาสที่แตกต่างกันCsvFileได้ แนะนำอินเทอร์เฟซเพียงเพื่อให้คุณสามารถพูดได้ว่าคุณมีเพศสัมพันธ์ลดลงเป็นโง่
tdammers

0

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

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

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

คนส่วนใหญ่ดูเหมือนจะสมมติว่าคุณCsvRecordเป็นชนชั้นที่มีคุณค่า ลองทำให้เป็นหนึ่ง ทำให้ไม่เปลี่ยนรูปถ้าคุณทำได้ หากคุณมีวัตถุสองตัวที่มีพอยน์เตอร์อยู่ด้วยกันให้นำวัตถุเหล่านั้นออกแล้วหาวิธีทำให้มันใช้งานได้ ค้นหาสถานที่เพื่อแยกคลาสและฟังก์ชัน ที่ที่ดีที่สุดในการแบ่งคลาสอาจไม่ตรงกับเลย์เอาต์ทางกายภาพของไฟล์เสมอไป ลองย้อนกลับความสัมพันธ์แม่ / ลูกของคลาส บางทีคุณอาจต้องการคลาสแยกต่างหากสำหรับการอ่านและเขียนไฟล์ csv บางทีคุณอาจต้องการคลาสแยกต่างหากสำหรับจัดการไฟล์ I / O และอินเทอร์เฟซไปยังเลเยอร์ด้านบน มีหลายสิ่งที่ต้องลองก่อนที่จะประกาศว่าไม่มีคุณค่า

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