คุณควรเลิกชั้นเรียนด้วยคำถาม
แต่ละชั้นควรทำภารกิจง่ายๆ ถ้างานของคุณซับซ้อนเกินกว่าจะทดสอบได้งานที่คลาสนั้นใหญ่เกินไป
ไม่สนใจความโง่ของการออกแบบนี้:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
UPDATE
ปัญหาเกี่ยวกับวิธีการขัดถูในชั้นเรียนคือคุณละเมิดการห่อหุ้ม การทดสอบของคุณควรตรวจสอบเพื่อดูว่าพฤติกรรมภายนอกของวัตถุตรงกับข้อกำหนด สิ่งที่เกิดขึ้นภายในวัตถุนั้นไม่มีธุรกิจอะไรเลย
ความจริงที่ว่า FullName ใช้ FirstName และ LastName เป็นรายละเอียดการนำไปใช้งาน ไม่มีสิ่งใดนอกชั้นเรียนที่ควรใส่ใจว่าเป็นเรื่องจริง โดยการเยาะเย้ยวิธีการสาธารณะเพื่อทดสอบวัตถุคุณกำลังทำการสันนิษฐานเกี่ยวกับวัตถุนั้น
ในบางจุดในอนาคตข้อสันนิษฐานนั้นอาจยุติลงอย่างถูกต้อง บางทีตรรกะชื่อทั้งหมดจะถูกย้ายไปยังวัตถุชื่อที่บุคคลเพียงแค่เรียก บางที FullName จะเข้าถึงตัวแปรสมาชิก first_name และ last_name โดยตรงจากนั้นเรียก FirstName และ LastName
คำถามที่สองคือสาเหตุที่คุณรู้สึกว่าจำเป็นต้องทำเช่นนั้น หลังจากทุกคลาสบุคคลของคุณสามารถทดสอบสิ่งที่ชอบ:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
คุณไม่ควรรู้สึกว่าต้องการส่วนใดสำหรับตัวอย่างนี้ ถ้าคุณทำเช่นนั้นคุณจะมีความสุขและหยุด ... หยุด! ไม่มีประโยชน์ที่จะเยาะเย้ยวิธีการที่นั่นเพราะคุณสามารถควบคุมสิ่งที่อยู่ในนั้นได้
กรณีเดียวที่ดูเหมือนว่าเหมาะสมสำหรับวิธีที่ FullName ใช้ในการเยาะเย้ยคือถ้า FirstName () และ LastName () เป็นวิธีการที่ไม่สำคัญ บางทีคุณอาจกำลังเขียนหนึ่งในเครื่องกำเนิดชื่อแบบสุ่มเหล่านั้นหรือ FirstName และนามสกุลแบบสอบถามแบบสอบถามฐานข้อมูลสำหรับคำตอบหรือบางสิ่งบางอย่าง แต่ถ้านั่นคือสิ่งที่เกิดขึ้นมันแนะนำว่าวัตถุกำลังทำสิ่งที่ไม่ได้อยู่ในคลาสบุคคล
กล่าวอีกนัยหนึ่งการเยาะเย้ยวิธีการคือการหยิบวัตถุและแยกออกเป็นสองส่วน ชิ้นหนึ่งถูกเยาะเย้ยในขณะที่ชิ้นส่วนอื่นจะถูกทดสอบ สิ่งที่คุณทำคือการแยกวัตถุออกเป็นส่วน ๆ หากเป็นกรณีนี้ให้แยกวัตถุออกแล้ว
หากชั้นเรียนของคุณเป็นแบบง่ายคุณไม่ควรรู้สึกว่าจำเป็นต้องเยาะเย้ยชิ้นส่วนของมันในระหว่างการทดสอบ หากชั้นเรียนของคุณซับซ้อนพอที่คุณจะรู้สึกว่าต้องเยาะเย้ยคุณควรแบ่งชั้นเรียนออกเป็นส่วน ๆ ที่เรียบง่าย
อัพเดทอีกครั้ง
อย่างที่ฉันเห็นวัตถุมีพฤติกรรมภายนอกและภายใน พฤติกรรมภายนอกรวมถึงการส่งคืนค่าการโทรเข้าไปในวัตถุอื่น ๆ ฯลฯ เห็นได้ชัดว่าสิ่งใดในหมวดนั้นควรได้รับการทดสอบ (ไม่งั้นคุณจะทดสอบอะไร) แต่พฤติกรรมภายในไม่ควรถูกทดสอบ
ขณะนี้มีการทดสอบพฤติกรรมภายในเนื่องจากเป็นสิ่งที่ส่งผลให้เกิดพฤติกรรมภายนอก แต่ฉันไม่ได้เขียนการทดสอบโดยตรงเกี่ยวกับพฤติกรรมภายในเพียงทางอ้อมผ่านพฤติกรรมภายนอก
ถ้าฉันต้องการทดสอบบางอย่างฉันคิดว่ามันควรจะถูกย้ายเพื่อให้มันกลายเป็นพฤติกรรมภายนอก นั่นเป็นเหตุผลที่ฉันคิดว่าถ้าคุณต้องการที่จะเยาะเย้ยบางสิ่งคุณควรแยกวัตถุเพื่อให้สิ่งที่คุณต้องการที่จะเยาะเย้ยตอนนี้อยู่ในพฤติกรรมภายนอกของวัตถุที่มีปัญหา
แต่มันแตกต่างกันอย่างไร ถ้า FirstName () และ LastName () เป็นสมาชิกของวัตถุอื่นจริง ๆ แล้วมันจะเปลี่ยนปัญหาของ FullName () หรือไม่? หากเราตัดสินใจว่าจำเป็นต้องจำลองชื่อจริงและนามสกุลจริงจะช่วยให้พวกเขาอยู่ในวัตถุอื่นได้หรือไม่
ฉันคิดว่าถ้าคุณใช้วิธีเยาะเย้ยคุณก็จะสร้างรอยต่อในวัตถุ คุณมีฟังก์ชั่นเช่น FirstName () และ LastName () ซึ่งสื่อสารโดยตรงกับแหล่งข้อมูลภายนอก คุณมี FullName () ซึ่งไม่มี แต่เนื่องจากพวกเขาทั้งหมดอยู่ในระดับเดียวกันที่ไม่ชัดเจน บางชิ้นไม่ควรเข้าถึงแหล่งข้อมูลโดยตรงและอื่น ๆ รหัสของคุณจะชัดเจนขึ้นหากแยกออกเป็นสองกลุ่ม
แก้ไข
ลองย้อนกลับไปถามว่าทำไมเราเลียนแบบวัตถุเมื่อเราทดสอบ
- ทำให้การทดสอบทำงานอย่างสม่ำเสมอ (หลีกเลี่ยงการเข้าถึงสิ่งที่เปลี่ยนแปลงจากการทำงานเป็นแบบเรียกใช้)
- หลีกเลี่ยงการเข้าถึงทรัพยากรที่มีราคาแพง (อย่ากดบริการของบุคคลที่สามเป็นต้น)
- ลดความซับซ้อนของระบบภายใต้การทดสอบ
- ทำให้ง่ายต่อการทดสอบสถานการณ์ที่เป็นไปได้ทั้งหมด (เช่นสิ่งต่าง ๆ เช่นการจำลองความล้มเหลว ฯลฯ )
- หลีกเลี่ยงการขึ้นอยู่กับรายละเอียดของรหัสส่วนอื่น ๆ เพื่อให้การเปลี่ยนแปลงในส่วนอื่น ๆ ของรหัสจะไม่ทำลายการทดสอบนี้
ตอนนี้ฉันคิดว่าเหตุผลที่ 1-4 ใช้ไม่ได้กับสถานการณ์นี้ การเยาะเย้ยแหล่งภายนอกเมื่อการทดสอบชื่อเต็มดูแลเหตุผลเหล่านั้นทั้งหมดสำหรับการเยาะเย้ย ชิ้นเดียวที่ไม่ได้จัดการนั่นคือความเรียบง่าย แต่ดูเหมือนว่าวัตถุนั้นง่ายพอที่ไม่ต้องกังวล
ฉันคิดว่าข้อกังวลของคุณคือเหตุผลข้อที่ 5 ข้อกังวลคือในบางจุดในอนาคตการเปลี่ยนแปลงการใช้งานของ FirstName และ LastName จะทำให้การทดสอบหยุดชะงัก ในอนาคตชื่อและนามสกุลอาจได้รับชื่อจากตำแหน่งที่ตั้งอื่นหรือแหล่งที่มา แต่ FullName FirstName() + " " + LastName()
อาจจะเสมอ นั่นเป็นเหตุผลที่คุณต้องการทดสอบ FullName โดยการจำลองชื่อและนามสกุล
สิ่งที่คุณมีก็คือเซตย่อยบางส่วนของวัตถุบุคคลซึ่งมีแนวโน้มที่จะเปลี่ยนแปลงมากกว่าคนอื่น ส่วนที่เหลือของวัตถุใช้ชุดย่อยนี้ ชุดย่อยนั้นดึงข้อมูลโดยใช้แหล่งข้อมูลหนึ่ง แต่อาจดึงข้อมูลนั้นในวิธีที่แตกต่างอย่างสิ้นเชิงในภายหลัง แต่สำหรับฉันดูเหมือนว่าเซตย่อยนั้นเป็นวัตถุที่แตกต่างพยายามออกไป
ดูเหมือนว่าถ้าคุณจำลองวิธีของวัตถุคุณจะแยกวัตถุออก แต่คุณกำลังทำในลักษณะเฉพาะกิจ รหัสของคุณไม่ได้ทำให้ชัดเจนว่ามีสองชิ้นที่แตกต่างกันภายในวัตถุบุคคลของคุณ ดังนั้นเพียงแยกวัตถุในรหัสจริงของคุณเพื่อให้ชัดเจนจากการอ่านรหัสของคุณสิ่งที่เกิดขึ้น เลือกการแบ่งวัตถุจริงที่เหมาะสมและอย่าพยายามแยกวัตถุนั้นแตกต่างกันสำหรับการทดสอบแต่ละครั้ง
ฉันสงสัยว่าคุณอาจคัดแยกวัตถุของคุณ แต่ทำไม?
แก้ไข
ฉันผิดไป.
คุณควรแยกวัตถุค่อนข้างแนะนำ ad-hoc splits โดยเยาะเย้ยแต่ละวิธี อย่างไรก็ตามฉันมุ่งเน้นไปที่วิธีแยกวัตถุ อย่างไรก็ตาม OO มีวิธีการแยกวัตถุหลายวิธี
สิ่งที่ฉันเสนอ:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
บางทีนั่นอาจเป็นสิ่งที่คุณทำมาตลอด แต่ฉันไม่คิดว่าวิธีนี้จะมีปัญหาที่ฉันเห็นด้วยวิธีการเยาะเย้ยเพราะเราอธิบายอย่างชัดเจนว่าแต่ละด้านเปิดอยู่อย่างไร และโดยใช้การสืบทอดเราจะหลีกเลี่ยงความอึดอัดใจที่จะเกิดขึ้นหากเราใช้วัตถุห่อหุ้มเพิ่มเติม
สิ่งนี้จะแนะนำความซับซ้อนบางอย่างและสำหรับฟังก์ชั่นยูทิลิตี้เพียงไม่กี่อย่างที่ฉันอาจจะทดสอบพวกเขาโดยเยาะเย้ยแหล่งที่มาของบุคคลที่สาม แน่นอนว่าพวกเขากำลังตกอยู่ในอันตรายที่เพิ่มขึ้นของการทำลาย แต่ไม่คุ้มค่าที่จะทำการจัดเรียง หากคุณมีวัตถุที่ซับซ้อนพอที่คุณต้องแยกมันออกมาฉันคิดว่านี่เป็นความคิดที่ดี