ฉันควรจะทดสอบคลาสย่อยหรือคลาสแม่นามธรรมของฉันหรือไม่


12

ฉันมีโครงร่างการใช้งานเช่นเดียวกับข้อ 18 จาก Effective Java (การอภิปรายเพิ่มเติมที่นี่ ) มันเป็นคลาสนามธรรมที่มีวิธีการสาธารณะ 2 วิธี methodA () และ methodB () ที่เรียกวิธีการ subclasses เพื่อ "เติมช่องว่าง" ที่ฉันไม่สามารถกำหนดในลักษณะที่เป็นนามธรรม

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

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

ปัญหาของฉันคือเมื่อฉันสร้างการใช้งานใหม่ฉันเขียนการทดสอบอีกครั้ง:

  • คลาสย่อยเรียกวิธีตามที่กำหนดหรือไม่?
  • วิธีการที่กำหนดมีคำอธิบายประกอบที่กำหนดหรือไม่
  • ฉันจะได้รับผลลัพธ์ที่คาดหวังจากวิธีการ A หรือไม่
  • ฉันจะได้รับผลลัพธ์ที่คาดหวังจากวิธีการ B หรือไม่

ในขณะที่เห็นข้อดีด้วยวิธีนี้:

  • เป็นเอกสารข้อกำหนดของคลาสย่อยผ่านการทดสอบ
  • มันอาจล้มเหลวอย่างรวดเร็วในกรณีที่มีการปรับโครงสร้างที่มีปัญหา
  • ทดสอบคลาสย่อยโดยรวมและสาธารณะ API ("methodA () ทำงานอย่างไร" และไม่ "วิธีนี้ได้รับการป้องกันหรือไม่")

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

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

ปัญหานี้เป็นเรื่องปกติหรือไม่เมื่อทำการทดสอบลำดับชั้น ฉันจะหลีกเลี่ยงสิ่งนั้นได้อย่างไร

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

ps2: ฉัน googled และพบคำถามมากมายเกี่ยวกับเรื่องนี้ หนึ่งในนั้นคืออันนี้ซึ่งมีคำตอบที่ดีจาก Nigel Thorne กรณีของฉันคือ "หมายเลข 1" ของเขา นั่นจะดี แต่ฉันไม่สามารถ refactor ณ จุดนี้ดังนั้นฉันจะต้องอยู่กับปัญหานี้ ถ้าฉันสามารถ refactor ฉันจะมีคลาสย่อยเป็นกลยุทธ์ นั่นจะเป็นเรื่องปกติ แต่ฉันจะยังคงทดสอบ "คลาสหลัก" และทดสอบกลยุทธ์ แต่ฉันจะไม่สังเกตเห็นการปรับโครงสร้างใด ๆ ที่ทำให้การรวมกลุ่มระหว่างคลาสหลักและกลยุทธ์แตก

ps3: ฉันพบคำตอบที่บอกว่า "เป็นที่ยอมรับ" เพื่อทดสอบระดับนามธรรม ฉันยอมรับว่านี่เป็นสิ่งที่ยอมรับได้ แต่ฉันต้องการที่จะรู้ว่าวิธีใดที่เป็นที่ต้องการในกรณีนี้ (ฉันยังคงเริ่มต้นด้วยการทดสอบหน่วย)


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

ฉันอ่านที่ไหนสักแห่งที่ไม่ควรใช้การสืบทอดในการทดสอบฉันกำลังมองหาแหล่งที่มาเพื่ออ่านอย่างระมัดระวัง
JSBach

4
@Kilian คุณแน่ใจหรือไม่เกี่ยวกับคำแนะนำนี้? ฟังดูเหมือนสูตรสำหรับการทดสอบแบบเปราะบางมีความไวสูงต่อการเปลี่ยนแปลงการออกแบบและไม่สามารถบำรุงรักษาได้มาก
Konrad Morawski

คำตอบ:


5

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

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


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

4

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

ข้อดีของวิธีนี้คือเนื่องจากคุณทำการทดสอบอินเตอร์เฟสคุณสามารถ refactor ภายใต้อินเตอร์เฟสได้ คุณอาจพบว่ารหัสในคลาสย่อยควรอยู่ในพาเรนต์หรือในทางกลับกัน ตอนนี้การทดสอบของคุณจะพิสูจน์ว่าคุณไม่ได้หยุดพฤติกรรมเมื่อคุณย้ายรหัสนั้น

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

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