ทำไมการทำซ้ำตัวเองด้วยการเขียนข้อสอบจึงได้รับการสนับสนุนอย่างมาก?
ดูเหมือนว่าการทดสอบโดยทั่วไปจะแสดงสิ่งเดียวกันกับรหัสและด้วยเหตุนี้จึงเป็นการทำซ้ำ (ในแนวคิดไม่ใช่การใช้งาน) ของโค้ด เป้าหมายสูงสุดของ DRY จะไม่รวมการกำจัดรหัสการทดสอบทั้งหมดหรือไม่
ทำไมการทำซ้ำตัวเองด้วยการเขียนข้อสอบจึงได้รับการสนับสนุนอย่างมาก?
ดูเหมือนว่าการทดสอบโดยทั่วไปจะแสดงสิ่งเดียวกันกับรหัสและด้วยเหตุนี้จึงเป็นการทำซ้ำ (ในแนวคิดไม่ใช่การใช้งาน) ของโค้ด เป้าหมายสูงสุดของ DRY จะไม่รวมการกำจัดรหัสการทดสอบทั้งหมดหรือไม่
คำตอบ:
ฉันเชื่อว่านี่เป็นความเข้าใจผิดที่ฉันสามารถคิดได้
รหัสทดสอบที่ทดสอบรหัสการผลิตนั้นไม่เหมือนกันทั้งหมด ฉันจะสาธิตด้วยไพ ธ อน:
def multiply(a, b):
"""Multiply ``a`` by ``b``"""
return a*b
จากนั้นการทดสอบอย่างง่ายจะเป็น:
def test_multiply():
assert multiply(4, 5) == 20
ฟังก์ชั่นทั้งสองมีคำจำกัดความที่คล้ายกัน แต่ทั้งสองทำสิ่งที่แตกต่างกันมาก ไม่มีรหัสซ้ำกันที่นี่ ;-)
นอกจากนี้ยังเกิดขึ้นที่คนเขียนการทดสอบที่ซ้ำกันเป็นหลักโดยมีการยืนยันหนึ่งข้อต่อการทดสอบ นี่คือความบ้าคลั่งและฉันได้เห็นคนทำเช่นนี้ นี่คือการปฏิบัติที่ไม่ดี
def test_multiply_1_and_3():
"""Assert that a multiplication of 1 and 3 is 3."""
assert multiply(1, 3) == 3
def test_multiply_1_and_7():
"""Assert that a multiplication of 1 and 7 is 7."""
assert multiply(1, 7) == 7
def test_multiply_3_and_4():
"""Assert that a multiplication of 3 and 4 is 12."""
assert multiply(3, 4) == 12
ลองนึกภาพการทำเช่นนี้กับโค้ดที่มีประสิทธิภาพกว่า 1,000 บรรทัด แต่คุณทดสอบบนพื้นฐานของ 'คุณสมบัติ':
def test_multiply_positive():
"""Assert that positive numbers can be multiplied."""
assert multiply(1, 3) == 3
assert multiply(1, 7) == 7
assert multiply(3, 4) == 12
def test_multiply_negative():
"""Assert that negative numbers can be multiplied."""
assert multiply(1, -3) == -3
assert multiply(-1, -7) == 7
assert multiply(-3, 4) == -12
ตอนนี้เมื่อคุณสมบัติเพิ่ม / ลบฉันต้องพิจารณาเพิ่ม / ลบฟังก์ชั่นการทดสอบหนึ่ง
คุณอาจสังเกตเห็นว่าฉันไม่ได้ใช้for
ลูป เนื่องจากการทำซ้ำบางสิ่งเป็นสิ่งที่ดี เมื่อฉันจะได้ใช้ลูปรหัสจะสั้นกว่ามาก แต่เมื่อการยืนยันล้มเหลวมันอาจทำให้สับสนกับผลลัพธ์ที่แสดงข้อความที่ไม่ชัดเจน หากสิ่งนี้เกิดขึ้นการทดสอบของคุณจะมีประโยชน์น้อยลงและคุณจะต้องมีโปรแกรมดีบั๊กเพื่อตรวจสอบว่ามีอะไรผิดพลาดบ้าง
assert multiply(1,3)
จะล้มเหลว แต่คุณจะไม่ได้รับรายงานการทดสอบที่ล้มเหลวassert multiply(3,4)
ด้วย
def test_shuffle
ดำเนินการสองชุด
assert multiply(*, *) == *
ดังนั้นคุณสามารถกำหนดassert_multiply
ฟังก์ชั่น ในสถานการณ์ปัจจุบันมันไม่สำคัญโดยการนับแถวและการอ่าน แต่การทดสอบที่ยาวนานคุณสามารถใช้การยืนยันที่ซับซ้อนการติดตั้งการสร้างรหัสการแข่งขัน ฯลฯ ... ฉันไม่รู้ว่านี่เป็นวิธีปฏิบัติที่ดีที่สุด แต่ฉันมักจะทำ นี้.
ดูเหมือนว่าการทดสอบโดยทั่วไปแสดงสิ่งเดียวกับรหัสและดังนั้นจึงซ้ำกัน
ไม่นี่ไม่เป็นความจริง
การทดสอบมีวัตถุประสงค์ที่แตกต่างจากการใช้งานของคุณ:
ไม่ DRY เป็นเรื่องเกี่ยวกับการเขียนโค้ดเพียงครั้งเดียวเพื่อทำภารกิจเฉพาะการทดสอบเป็นการตรวจสอบความถูกต้องว่างานนั้นทำอย่างถูกต้อง มันค่อนข้างคล้ายกับอัลกอริทึมการลงคะแนนที่เห็นได้ชัดว่าการใช้รหัสเดียวกันจะไร้ประโยชน์
เป้าหมายสูงสุดของ DRY จะไม่รวมการกำจัดรหัสการทดสอบทั้งหมดหรือไม่
ไม่มีเป้าหมายสูงสุดของแห้งจะกำจัดจริงเฉลี่ยของทุกรหัสการผลิต
หากการทดสอบของเราเป็นข้อมูลจำเพาะที่สมบูรณ์แบบในสิ่งที่เราต้องการให้ระบบทำเราจะต้องสร้างรหัสการผลิตที่สอดคล้องกัน (หรือไบนารี) โดยอัตโนมัติโดยลบฐานการผลิตตามรหัสอย่างมีประสิทธิภาพ
นี่คือสิ่งที่จริง ๆ แล้ววิธีการเช่นสถาปัตยกรรมที่ขับเคลื่อนด้วยโมเดลอ้างว่าบรรลุผลซึ่งเป็นแหล่งความจริงที่มนุษย์ออกแบบขึ้นมาโดยเฉพาะซึ่งทุกอย่างได้มาจากการคำนวณ
ฉันไม่คิดว่าสิ่งที่ตรงกันข้าม (กำจัดการทดสอบทั้งหมด) เป็นที่ต้องการเพราะ:
เพราะบางครั้งการทำซ้ำตัวเองก็โอเค ไม่มีการยึดถือหลักการเหล่านี้ในทุกสถานการณ์โดยไม่มีคำถามหรือบริบท บางครั้งฉันมีการทดสอบเขียนกับอัลกอริทึมรุ่น na versionve (และช้า) ซึ่งเป็นการละเมิด DRY ที่ค่อนข้างชัดเจน แต่เป็นประโยชน์อย่างแน่นอน
เนื่องจากการทดสอบหน่วยกำลังเกี่ยวกับการเปลี่ยนแปลงโดยไม่ตั้งใจให้หนักขึ้นบางครั้งก็สามารถทำการเปลี่ยนแปลงโดยเจตนาได้ยากขึ้นเช่นกัน ความจริงเรื่องนี้เกี่ยวข้องกับหลักการของ DRY
ตัวอย่างเช่นหากคุณมีฟังก์ชั่นMyFunction
ที่เรียกว่าในรหัสการผลิตในที่เดียวและคุณเขียนการทดสอบ 20 หน่วยสำหรับมันคุณสามารถจบลงด้วยการมี 21 สถานที่ในรหัสของคุณที่เรียกว่าฟังก์ชั่น ตอนนี้เมื่อคุณต้องเปลี่ยนลายเซ็นของMyFunction
หรือความหมายหรือทั้งสองอย่าง (เนื่องจากข้อกำหนดบางประการมีการเปลี่ยนแปลง) คุณมี 21 ที่ที่จะเปลี่ยนแทนที่จะเป็นที่เดียว และเหตุผลก็คือการละเมิดหลักการ DRY: คุณเรียกใช้ฟังก์ชั่นเดิมซ้ำ ๆ (อย่างน้อย) ถึงMyFunction
21 ครั้ง
วิธีการที่ถูกต้องสำหรับกรณีเช่นนี้คือการนำหลักการ DRY มาใช้กับรหัสการทดสอบของคุณเช่นกัน: เมื่อเขียนหน่วยทดสอบ 20 ชุดให้สรุปการเรียกใช้MyFunction
ในการทดสอบหน่วยของคุณด้วยฟังก์ชั่นตัวช่วยเพียงไม่กี่ตัว ทดสอบ 20 หน่วย เป็นการดีที่คุณจะได้เพียงสองสถานที่ในการเรียกรหัสของคุณMyFunction
: หนึ่งจากรหัสการผลิตของคุณและหนึ่งจากการทดสอบหน่วยคุณ ดังนั้นเมื่อคุณต้องเปลี่ยนลายเซ็นต์ของMyFunction
ภายหลังคุณจะมีเพียงไม่กี่ที่ที่จะเปลี่ยนในการทดสอบของคุณ
"สถานที่ไม่กี่แห่ง" ยังคงเป็นมากกว่า "สถานที่แห่งเดียว" (สิ่งที่คุณได้รับโดยไม่มีการทดสอบหน่วยเลย) แต่ข้อดีของการมีการทดสอบหน่วยควรหนักกว่าข้อดีของการมีรหัสน้อยกว่าในการเปลี่ยนแปลง ไม่ถูกต้อง).
หนึ่งในความท้าทายที่ยิ่งใหญ่ที่สุดในการสร้างซอฟต์แวร์คือการจับความต้องการ เพื่อตอบคำถาม"ซอฟต์แวร์นี้ควรทำอย่างไร" ซอฟต์แวร์ต้องการข้อกำหนดที่แน่นอนเพื่อกำหนดสิ่งที่ระบบต้องทำอย่างแม่นยำ แต่ผู้ที่กำหนดความต้องการสำหรับระบบซอฟต์แวร์และโครงการมักจะรวมถึงผู้ที่ไม่มีซอฟต์แวร์หรือพื้นหลัง (คณิตศาสตร์) ที่เป็นทางการ การขาดข้อกำหนดที่เข้มงวดในการกำหนดข้อกำหนดบังคับให้มีการพัฒนาซอฟต์แวร์เพื่อหาวิธีในการตรวจสอบความถูกต้องของซอฟต์แวร์ตามข้อกำหนด
ทีมพัฒนาพบว่าตัวเองแปลคำอธิบายภาษาสำหรับโครงการเป็นข้อกำหนดที่เข้มงวดมากขึ้น วินัยการทดสอบได้รวมกันเป็นจุดตรวจสอบสำหรับการพัฒนาซอฟต์แวร์เพื่อเชื่อมช่องว่างระหว่างสิ่งที่ลูกค้าบอกว่าพวกเขาต้องการและสิ่งที่ซอฟต์แวร์เข้าใจว่าพวกเขาต้องการ ทั้งนักพัฒนาซอฟต์แวร์และทีมงานด้านคุณภาพ / การทดสอบมีความเข้าใจในข้อกำหนด (ไม่เป็นทางการ) และแต่ละคน (อิสระ) เขียนซอฟต์แวร์หรือทดสอบเพื่อให้แน่ใจว่าความเข้าใจของพวกเขาสอดคล้อง การเพิ่มบุคคลอื่นเพื่อทำความเข้าใจกับข้อกำหนด (ไม่แน่ชัด) เพิ่มคำถามและมุมมองที่แตกต่างเพื่อเสริมความแม่นยำของข้อกำหนด
เนื่องจากมีการทดสอบการยอมรับมาตลอดจึงเป็นเรื่องปกติที่จะขยายบทบาทการทดสอบเพื่อเขียนการทดสอบอัตโนมัติและหน่วยการเรียนรู้ ปัญหาคือนั่นหมายถึงการว่าจ้างโปรแกรมเมอร์ให้ทำการทดสอบและทำให้คุณ จำกัด มุมมองจากการประกันคุณภาพไปจนถึงโปรแกรมเมอร์ที่ทำการทดสอบ
ทั้งหมดที่กล่าวมาคุณอาจทำการทดสอบผิดถ้าการทดสอบของคุณแตกต่างจากโปรแกรมจริงเล็กน้อย ข้อเสนอแนะ Msdy จะให้ความสำคัญกับสิ่งที่อยู่ในการทดสอบมากขึ้นและน้อยลงเกี่ยวกับวิธีการ
ประชดคือแทนที่จะจับข้อกำหนดอย่างเป็นทางการของข้อกำหนดจากคำอธิบายภาษาอุตสาหกรรมได้เลือกที่จะใช้การทดสอบจุดเป็นรหัสเพื่อการทดสอบอัตโนมัติ แทนที่จะผลิตข้อกำหนดอย่างเป็นทางการซึ่งซอฟต์แวร์สามารถสร้างขึ้นเพื่อตอบคำถามวิธีการที่ได้รับคือการทดสอบไม่กี่จุดแทนที่จะเข้าใกล้ซอฟต์แวร์สร้างโดยใช้ตรรกะอย่างเป็นทางการ นี่คือการประนีประนอม แต่มีประสิทธิภาพและค่อนข้างประสบความสำเร็จ
หากคุณคิดว่ารหัสทดสอบของคุณคล้ายกับรหัสการนำไปใช้งานของคุณมากเกินไปนี่อาจเป็นข้อบ่งชี้ว่าคุณกำลังใช้เฟรมเวิร์กในการจำลองมากเกินไป การทดสอบแบบจำลองที่ระดับต่ำเกินไปอาจจบลงด้วยการตั้งค่าการทดสอบที่ดูคล้ายกับวิธีการทดสอบ ลองเขียนการทดสอบระดับสูงที่มีโอกาสน้อยกว่าถ้าคุณเปลี่ยนการใช้งาน (ฉันรู้ว่าสิ่งนี้อาจเป็นเรื่องยาก แต่ถ้าคุณสามารถจัดการได้คุณจะมีชุดการทดสอบที่มีประโยชน์มากกว่านี้)
การทดสอบหน่วยไม่ควรรวมถึงการทำซ้ำรหัสภายใต้การทดสอบตามที่ได้รับการบันทึกแล้ว
ฉันจะเพิ่มแม้ว่าการทดสอบหน่วยโดยทั่วไปจะไม่เป็น DRY เป็นรหัส "การผลิต" เนื่องจากการตั้งค่ามีแนวโน้มที่จะคล้ายกัน (แต่ไม่เหมือนกัน) ในการทดสอบ ... โดยเฉพาะอย่างยิ่งถ้าคุณมีการพึ่งพาจำนวนมาก / แกล้งทำ
แน่นอนว่ามันเป็นไปได้ที่จะปรับสิ่งต่าง ๆ ให้เป็นวิธีการตั้งค่าทั่วไป (หรือชุดวิธีการตั้งค่า) ... แต่ฉันพบว่าวิธีการตั้งค่าเหล่านั้นมักจะมีรายการพารามิเตอร์ที่ยาวและค่อนข้างจะเปราะบาง
ดังนั้นควรจะเน้นในทางปฏิบัติ หากคุณสามารถรวมรหัสการตั้งค่าได้โดยไม่ส่งผลกระทบต่อการบำรุงรักษา แต่อย่างใด แต่ถ้าทางเลือกนั้นเป็นวิธีการตั้งค่าที่ซับซ้อนและเปราะบางการทำซ้ำเล็กน้อยในวิธีทดสอบของคุณก็โอเค
ผู้สอนศาสนาท้องถิ่น TDD / BDD กล่าวแบบนี้:
"รหัสการผลิตของคุณควรเป็น DRY แต่มันก็โอเคสำหรับการทดสอบของคุณที่จะ 'ชื้น'
ดูเหมือนว่าการทดสอบโดยทั่วไปจะแสดงสิ่งเดียวกันกับรหัสและด้วยเหตุนี้จึงเป็นการทำซ้ำ (ในแนวคิดไม่ใช่การใช้งาน) ของโค้ด
สิ่งนี้ไม่เป็นความจริงการทดสอบอธิบายกรณีการใช้งานในขณะที่รหัสอธิบายถึงอัลกอริทึมที่ผ่านกรณีการใช้งานซึ่งเป็นเรื่องทั่วไปมากขึ้น โดย TDD คุณเริ่มต้นด้วยการเขียนกรณีการใช้งาน (อาจขึ้นอยู่กับเรื่องราวของผู้ใช้) และหลังจากนั้นคุณก็ใช้โค้ดที่จำเป็นเพื่อส่งกรณีการใช้งานเหล่านี้ ดังนั้นคุณเขียนการทดสอบขนาดเล็กรหัสเล็ก ๆ และหลังจากนั้นคุณปรับโครงสร้างอีกครั้งหากจำเป็นเพื่อกำจัดการทำซ้ำ มันเป็นวิธีการทำงาน
โดยการทดสอบสามารถทำซ้ำได้เช่นกัน ตัวอย่างเช่นคุณสามารถใช้การแข่งขันการสร้างรหัสการติดตั้งการยืนยันที่ซับซ้อน ฯลฯ ... ฉันมักจะทำสิ่งนี้เพื่อป้องกันข้อผิดพลาดในการทดสอบ แต่ฉันมักจะลืมทดสอบก่อนว่าการทดสอบล้มเหลวจริง ๆ หรืออาจทำลายวัน เมื่อคุณกำลังมองหาข้อผิดพลาดในรหัสครึ่งชั่วโมงและการทดสอบผิด ... xD