TDD Red-Green-Refactor และหาก / วิธีการทดสอบวิธีการที่เป็นส่วนตัว


91

เท่าที่ฉันเข้าใจคนส่วนใหญ่ดูเหมือนจะยอมรับว่าวิธีการส่วนตัวไม่ควรทดสอบโดยตรง แต่ผ่านวิธีการสาธารณะใด ๆ เรียกพวกเขา ฉันสามารถเห็นประเด็นของพวกเขา แต่ฉันมีปัญหาบางอย่างกับเรื่องนี้เมื่อฉันพยายามติดตาม "สามกฏของ TDD" และใช้วงจร "สีแดง - เขียว - refactor" ฉันคิดว่าตัวอย่างอธิบายได้ดีที่สุด:

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

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

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

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

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

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

แล้วจะทำอย่างไรดี? TDD (ที่มีรอบการเปลี่ยนสีเขียว - แดงอย่างรวดเร็ว) นั้นเข้ากันไม่ได้กับวิธีการส่วนตัวหรือไม่? หรือมีข้อบกพร่องในการออกแบบของฉัน?



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

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

7
ฉันไม่ชอบ TDD ด้วยเหตุผลนี้: หากคุณอยู่ในพื้นที่ใหม่คุณจะต้องทำงานพิเศษมากมายในขณะที่พยายามค้นหาว่าสถาปัตยกรรมควรเป็นอย่างไรและทำงานอย่างไร ในทางตรงกันข้าม: หากคุณอยู่ในพื้นที่ที่คุณเคยมีประสบการณ์แล้วจะมีประโยชน์ในการเขียนการทดสอบครั้งแรกนอกเหนือจากที่น่ารำคาญคุณเพราะ Intellisense ไม่เข้าใจว่าทำไมคุณเขียนรหัสที่ไม่สามารถคอมไพล์ได้ ฉันเป็นแฟนตัวยงของการออกแบบเขียนและทดสอบหน่วย
Jeroen Vannevel

1
"คนส่วนใหญ่ดูเหมือนจะยอมรับว่าวิธีการส่วนตัวไม่ควรทดสอบโดยตรง" - ไม่ทดสอบวิธีโดยตรงหากเหมาะสม ซ่อนไว้ราวกับprivateว่ามันเหมาะสมที่จะทำ
osa

คำตอบ:


44

หน่วย

ฉันคิดว่าฉันสามารถระบุได้อย่างแม่นยำว่าปัญหาเริ่มต้นที่ใด:

ฉันคิดว่าฉันจะต้องใช้วิธีการที่พบเขตข้อมูลที่ไม่ใช่ตัวเลขทั้งหมดในบรรทัด

สิ่งนี้ควรตามมาทันทีด้วยการถามตัวเองว่า "จะเป็นหน่วยทดสอบที่แยกออกไปgatherNonNumericColumnsหรือส่วนหนึ่งของหน่วยทดสอบเดียวกันได้หรือไม่"

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

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


การรักษารอบสั้น

การดำเนินการด้านบนไม่ควรนำไปสู่ปัญหาที่คุณอธิบายไว้ในย่อหน้าสุดท้าย:

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

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


Roadmap

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


2
ฉันคิดว่านี่เป็นคำตอบที่นี่ การใช้ TDD ในสภาพแวดล้อมแบบ OOP ฉันมักจะพบว่าตัวเองมีช่วงเวลาที่ยากลำบากในการเอาชนะสัญชาตญาณจากล่างขึ้นบนของตัวเอง ใช่ฟังก์ชั่นควรมีขนาดเล็ก แต่นั่นก็เป็นหลังจากการปรับโครงสร้างใหม่ ก่อนหน้านี้พวกเขาสามารถเป็นเสาหินขนาดใหญ่ได้ +1
João Mendes

2
@ JoãoMendesดีฉันไม่แน่ใจว่าคุณควรไปถึงสถานะของหินใหญ่ก้อนเดียวก่อนที่จะ refactoring โดยเฉพาะอย่างยิ่งในรอบ RGR สั้นมาก แต่ใช่ในหน่วยที่ทดสอบได้การทำงานจากล่างขึ้นบนอาจนำไปสู่ปัญหาที่ OP อธิบาย
Ben Aaronson

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

66

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

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

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

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

ฉันคิดว่าหลายคนพยายามที่จะเปลี่ยนวิธีการส่วนตัวให้เป็นหน่วยที่ทดสอบได้เพราะมันดูเหมือนจะง่ายต่อการทดสอบ แต่มันใช้เวลานานมากในการทดสอบ Martin Fowler กล่าว

แม้ว่าฉันจะเริ่มต้นด้วยแนวคิดของหน่วยการเรียนฉันมักจะใช้เวลาเรียนที่เกี่ยวข้องอย่างใกล้ชิดและถือว่าพวกเขาเป็นหน่วยเดียว

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


14
สำหรับฉันดูเหมือนว่าคำตอบนี้จะไม่สนใจวิธีการ TDD อย่างสมบูรณ์ในคำถามของ OP มันเป็นเพียงการทำซ้ำของมนต์ "ไม่ทดสอบวิธีส่วนตัว" แต่มันไม่ได้อธิบายว่า TDD - ซึ่งเป็นวิธีการตามจริง - อาจทำงานร่วมกับวิธีการทดสอบหน่วยที่ไม่ใช่วิธี
Doc Brown

6
@DocBrown ไม่มันตอบเขาอย่างสมบูรณ์ในการพูดว่า "ไม่เกินแกรนูล" หน่วยของคุณและทำให้ชีวิตยากขึ้นสำหรับตัวคุณเอง TDD ไม่ใช่วิธีการ แต่เป็นหน่วยตามที่หน่วยเป็นสิ่งที่ทำให้รู้สึก หากคุณมีไลบรารี่ C แล้วใช่แต่ละยูนิตจะเป็นฟังก์ชั่น หากคุณมีคลาสหน่วยเป็นวัตถุ ดังที่ Fowler กล่าวบางครั้งหน่วยเรียนจะมีคลาสที่เกี่ยวข้องกันหลายชั้น ฉันคิดว่าหลายคนคิดว่าการทดสอบหน่วยเป็นแบบทีละวิธีเนื่องจากเครื่องมือโง่บางอย่างสร้างสตับโดยใช้วิธีการ
gbjbaanb

3
@gbjbaanb: พยายามแนะนำการทดสอบที่อนุญาตให้ OP ใช้ "รวบรวมฟิลด์ที่ไม่ใช่ตัวเลขในบรรทัด" โดยใช้ TDD บริสุทธิ์ก่อนโดยไม่ต้องมีส่วนต่อประสานสาธารณะของคลาสที่เขาตั้งใจจะเขียนแล้ว
Doc Brown

8
ฉันต้องเห็นด้วยกับ @DocBrown ที่นี่ ปัญหาของผู้ถามไม่ใช่ว่าเขาต้องการการทดสอบที่ละเอียดกว่าที่เขาสามารถทำได้โดยไม่ต้องทดสอบวิธีการส่วนตัว เขาพยายามที่จะทำตามแนวทาง TDD อย่างเข้มงวดและ - โดยไม่ได้วางแผนเช่นนั้น - ซึ่งทำให้เขาตีกำแพงที่เขาพบว่าเขามีการทดสอบหลายอย่างว่าอะไรควรเป็นวิธีการส่วนตัว คำตอบนี้ไม่ได้ช่วย มันเป็นคำตอบที่ดีสำหรับคำถามไม่ใช่แค่คำถามนี้
Ben Aaronson

7
@ Matthew: ความผิดพลาดของเขาคือการที่เขาเขียนฟังก์ชั่นตั้งแต่แรก โดยหลักการแล้วเขาควรจะเขียนวิธีการสาธารณะเป็นรหัสสปาเก็ตตี้แล้ว refactor มันออกมาเป็นฟังก์ชั่นส่วนตัวในวงจร refactor - ไม่ทำเครื่องหมายว่าเป็นส่วนตัวในวงจร refactor
slebetman

51

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

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

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


ใช่ฉันเห็นด้วยกับคุณ. แต่ฉันมีปัญหากับคำสั่งแรกของคุณทั้งส่วน "ซับซ้อนพอ" และส่วน "แยกพอ" เกี่ยวกับ "ซับซ้อนเพียงพอ": ฉันกำลังพยายามทำวงจรสีแดง - เขียวอย่างรวดเร็วซึ่งหมายความว่าฉันสามารถกำหนดรหัสได้ไม่เกินหนึ่งนาทีต่อครั้งก่อนที่จะเปลี่ยนเป็นการทดสอบ (หรือวิธีอื่น ๆ ) นั่นหมายความว่าการทดสอบของฉันจะมีความละเอียดมากแน่นอน ฉันคิดว่านั่นเป็นหนึ่งในข้อได้เปรียบของ TDD แต่บางทีฉันก็ใช้เงินไปมากจนทำให้มันเสียเปรียบ
Henrik Berg

เกี่ยวกับ "แยกพอ": ฉันได้เรียนรู้ (อีกครั้งจากลุงบอบ) ว่าฟังก์ชั่นควรมีขนาดเล็กและควรมีขนาดเล็กกว่านั้น โดยพื้นฐานแล้วฉันพยายามทำฟังก์ชั่น 3-4 บรรทัด ดังนั้นฟังก์ชั่นทั้งหมดจะถูกแยกออกเป็นวิธีการของตัวเองไม่ว่าจะเล็กหรือเรียบง่าย
Henrik Berg

อย่างไรก็ตามฉันรู้สึกเหมือนแง่มุมข้อมูลที่น่าสงสัย (เช่น findNonNumericFields) ควรเป็นส่วนตัวจริงๆ และถ้าฉันแยกมันออกเป็นคลาสอื่นฉันจะต้องเปิดเผยต่อไปดังนั้นฉันจึงไม่ค่อยเห็นประเด็นในเรื่องนั้น
Henrik Berg

6
@HenrikBerg คิดว่าทำไมคุณมีวัตถุในตอนแรก - มันไม่ใช่วิธีที่สะดวกในการจัดกลุ่มฟังก์ชั่น แต่เป็นหน่วยที่อยู่ในตัวเองซึ่งทำให้ระบบที่ซับซ้อนทำงานได้ง่ายขึ้น ดังนั้นคุณควรคิดถึงการทดสอบระดับเป็นสิ่ง
gbjbaanb

@gbjbaanb ฉันจะยืนยันว่าพวกเขาทั้งสองอย่างเดียวกัน
RubberDuck

29

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

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

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


1
"รายละเอียดการใช้งาน" เป็นสิ่งที่ "ฉันใช้ XOR หรือตัวแปรชั่วคราวเพื่อแลกเปลี่ยน ints ระหว่างตัวแปร" วิธีการป้องกัน / ส่วนตัวมีสัญญาเหมือนอย่างอื่น พวกเขารับอินพุตทำงานกับมันและสร้างเอาต์พุตบางส่วนภายใต้ข้อ จำกัด บางอย่าง ในที่สุดสิ่งใดก็ตามที่มีสัญญาควรได้รับการทดสอบ - ไม่จำเป็นสำหรับผู้ที่ใช้ห้องสมุดของคุณ แต่สำหรับผู้ที่ดูแลรักษาและแก้ไขมันหลังจากคุณ เพียงเพราะมันไม่ได้เป็น "สาธารณะ" ไม่ได้หมายความว่ามันไม่ได้เป็นส่วนหนึ่งของAPI
Knetic

11

คุณไม่ได้ทำ TDD ตามสิ่งที่คุณคาดหวังว่าชั้นจะทำภายใน

กรณีทดสอบของคุณควรเป็นไปตามคลาส / ฟังก์ชัน / โปรแกรมที่ต้องทำกับโลกภายนอก ในตัวอย่างของคุณผู้ใช้จะเคยเรียกคลาสผู้อ่านของคุณด้วยถึงfind all the non-numerical fields in a line?

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

การไหลของ TDD คือ:

  • สีแดง (คลาส / วัตถุ / ฟังก์ชัน / ฯลฯ ทำกับโลกภายนอก) คืออะไร
  • เขียว (เขียนรหัสขั้นต่ำเพื่อให้ฟังก์ชันโลกภายนอกทำงานได้)
  • refactor (รหัสที่ดีกว่าคืออะไรที่จะทำให้การทำงานนี้)

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

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

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

9

คุณกำลังเผชิญกับความเข้าใจผิดที่พบบ่อยกับการทดสอบโดยทั่วไป

คนส่วนใหญ่ที่ยังใหม่กับการทดสอบเริ่มคิดด้วยวิธีนี้:

  • เขียนทดสอบฟังก์ชั่น F
  • ใช้ F
  • เขียนทดสอบสำหรับฟังก์ชัน G
  • ใช้ G โดยใช้การเรียกไปยัง F
  • เขียนทดสอบสำหรับฟังก์ชัน H
  • ใช้งาน H โดยใช้การเรียกร้องให้ G

และอื่น ๆ

ปัญหาคือที่นี่คุณจริง ๆ แล้วไม่มีหน่วยทดสอบสำหรับฟังก์ชั่น H การทดสอบที่ควรทดสอบ H จริง ๆ แล้วทดสอบ H, G และ F ในเวลาเดียวกัน

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

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


3
ฉันหวังว่าฉันจะสามารถลงคะแนนนี้หลาย ๆ ครั้ง ฉันจะสรุปมันเพราะคุณไม่ได้แก้ปัญหาให้ถูกต้อง
Justin Ohms

ในภาษาอย่าง C สิ่งนี้สมเหตุสมผล แต่สำหรับภาษา OO ที่หน่วยควรเป็นคลาส (ด้วยวิธีสาธารณะและส่วนตัว) จากนั้นคุณควรทดสอบคลาสไม่ใช่วิธีส่วนตัวทุกวิธีแยกกัน แยกชั้นเรียนของคุณใช่ การแยกวิธีการในแต่ละชั้นไม่มี
gbjbaanb

8

การทดสอบที่คุณเขียนในระหว่างการทดสอบการพัฒนาขับเคลื่อนนั้นควรทำให้แน่ใจว่าคลาสนั้นใช้ API สาธารณะอย่างถูกต้องในขณะเดียวกันก็ทำให้แน่ใจว่า API สาธารณะนั้นง่ายต่อการทดสอบและใช้งาน

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

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

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


4

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

(ฉันไม่ชัดเจนเลยว่าฟังก์ชั่นของคุณทำอะไรมันส่งคืนสตริงด้วยเนื้อหาไฟล์ที่มีค่าที่ไม่ใช่ตัวเลขหรือไม่?)

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

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


3

มันให้ความรู้สึกเหมือนฉันวาดตัวเองเป็นมุมตรงนี้ แต่ฉันล้มเหลวตรงไหน

มีสุภาษิตโบราณ

เมื่อคุณล้มเหลวในการวางแผนคุณวางแผนที่จะล้มเหลว

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

อ้าปากค้างฉันเขียนบางรหัส "" ก่อนที่จะเขียนการทดสอบใด ๆ ! ไม่เลย ฉันไม่ได้ ผมเขียนสัญญาที่จะปฏิบัติตามที่ออกแบบ ฉันสงสัยว่าคุณจะได้รับผลลัพธ์ที่คล้ายกันโดยการจดไดอะแกรม UML ลงบนกระดาษกราฟ ประเด็นคือคุณต้องมีแผน TDD ไม่ใช่ใบอนุญาตในการแฮ็คโดยเจตนาในโค้ด

ฉันรู้สึกว่า "การทดสอบครั้งแรก" นั้นเป็นชื่อเรียกที่ผิด ออกแบบก่อน จากนั้นทดสอบ

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


2

จำไว้ว่าการทดสอบนั้นสามารถสร้างขึ้นมาใหม่ได้ หากคุณทำให้วิธีการเป็นส่วนตัวคุณกำลังลด API สาธารณะและดังนั้นจึงเป็นที่ยอมรับอย่างสมบูรณ์ในการทิ้งการทดสอบที่เกี่ยวข้องบางอย่างสำหรับ "การทำงานที่เสียไป" (AKA ลดความซับซ้อน)

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

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

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


1
ในขณะที่คำตอบอื่น ๆ นั้นถูกต้องในวิธีการส่วนตัวที่ไม่ควรเขียนในรอบสีแดงมนุษย์ทำผิดพลาด และเมื่อคุณก้าวลงไปสู่เส้นทางแห่งความผิดพลาดที่มากพอนี่คือทางออกที่เหมาะสม
slebetman

2

มีบางครั้งที่วิธีการส่วนตัวสามารถทำวิธีสาธารณะในชั้นเรียนอื่น

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

(สิ่งนี้ชัดเจนมากที่สุดเมื่อคุณคิดว่าไม่มีคลาส List และถ้าคุณพยายามใช้ฟังก์ชัน List เป็นเมธอดส่วนตัวในคลาสที่ใช้งาน)


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

0

ฉันสงสัยว่าทำไมภาษาของคุณมีความเป็นส่วนตัวสองระดับเท่านั้นเป็นสาธารณะและเป็นส่วนตัวโดยสมบูรณ์

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

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


3
การเปลี่ยนโมดิฟายเออร์ในการเข้าถึงวิธีการเพียงเพื่อเรียกใช้การทดสอบเป็นสถานการณ์ที่หางสุนัขกระดิกสุนัข ฉันคิดว่าการทดสอบส่วนในของหน่วยเป็นเพียงการทำให้ยากขึ้นอีกครั้งในภายหลัง การทดสอบส่วนต่อประสานสาธารณะนั้นดีมากเพราะทำงานเป็น "สัญญา" สำหรับหน่วย
scriptin

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

0

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


0

ฉันเคยรู้สึกแบบนี้และรู้สึกถึงความเจ็บปวดของคุณ

ทางออกของฉันคือ:

หยุดทำการทดสอบเช่นการสร้างเสาหิน

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

ตัวอย่างเช่นฉันมักจะมี:

  • แบบทดสอบระดับต่ำ 1
  • รหัสเพื่อตอบสนองมัน
  • การทดสอบระดับต่ำ 2
  • รหัสเพื่อตอบสนองมัน
  • แบบทดสอบระดับต่ำ 3
  • รหัสเพื่อตอบสนองมัน
  • การทดสอบระดับต่ำ 4
  • รหัสเพื่อตอบสนองมัน
  • แบบทดสอบระดับต่ำ 5
  • รหัสเพื่อตอบสนองมัน

ดังนั้นฉันมี

  • แบบทดสอบระดับต่ำ 1
  • การทดสอบระดับต่ำ 2
  • แบบทดสอบระดับต่ำ 3
  • การทดสอบระดับต่ำ 4
  • แบบทดสอบระดับต่ำ 5

แต่ถ้าตอนนี้ฉันจะเพิ่มฟังก์ชั่นระดับที่สูงขึ้น (s) ที่เรียกว่าที่มีจำนวนมากของการทดสอบผมอาจจะสามารถที่จะลดตอนนี้ผู้ทดสอบระดับต่ำเพียงแค่จะ:

  • แบบทดสอบระดับต่ำ 1
  • แบบทดสอบระดับต่ำ 5

มารอยู่ในรายละเอียดและความสามารถในการทำมันจะขึ้นอยู่กับสถานการณ์


-2

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

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


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