รหัสที่ซ้ำกันสามารถยอมรับได้มากกว่าในการทดสอบหน่วยหรือไม่?


113

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

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

คำตอบ:


68

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

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

หากการทำซ้ำอยู่ในรหัสที่จัดการกับมทส. ให้ถามตัวเองว่าเหตุใดการทดสอบ "หน่วย" หลายรายการจึงใช้ฟังก์ชันเดียวกันทั้งหมด

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

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

บางทีคุณอาจต้องใช้assertPersonEqualวิธีการเดียวจึงจะสามารถเขียนassertPersonEqual(Person('Joe', 'Bloggs', 23), person)ได้ (หรือบางทีคุณอาจต้องโอเวอร์โหลดตัวดำเนินการความเท่าเทียมกันPerson)

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

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


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

15
แต่เพื่อป้องกันรหัสซ้ำในการทดสอบหน่วยคุณมักจะต้องแนะนำตรรกะใหม่ ฉันไม่คิดว่าการทดสอบหน่วยควรมีตรรกะเพราะคุณจะต้องมีการทดสอบหน่วยของการทดสอบหน่วย
Petr Peller

@ user11617 โปรดกำหนด "API ส่วนตัว" และ "API สาธารณะ" ตามความเข้าใจของฉัน Api สาธารณะคือ Api ที่มองเห็นได้สำหรับผู้บริโภคภายนอก / บุคคลภายนอกและกำหนดเวอร์ชันผ่าน SemVer หรือที่คล้ายกันอย่างชัดเจนสิ่งอื่นใดเป็นแบบส่วนตัว ด้วยคำจำกัดความนี้การทดสอบหน่วยเกือบทั้งหมดกำลังทดสอบ "API ส่วนตัว" และด้วยเหตุนี้จึงมีความไวต่อการทำซ้ำโค้ดมากขึ้นซึ่งฉันคิดว่าเป็นเรื่องจริง
KolA

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

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

186

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

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


xUnit และอื่น ๆ มีอาร์กิวเมนต์ "ข้อความ" ในการยืนยันการโทร เป็นความคิดที่ดีที่จะใส่วลีที่มีความหมายเพื่อให้นักพัฒนาค้นหาผลการทดสอบที่ล้มเหลวได้อย่างรวดเร็ว
seand

1
@seand คุณสามารถพยายามอธิบายว่าสิ่งที่คุณยืนยันกำลังตรวจสอบอยู่ แต่เมื่อมันล้มเหลวและมีโค้ดที่คลุมเครืออยู่นักพัฒนาก็จะต้องดำเนินการต่อไป IMO สำคัญกว่าที่จะต้องมีรหัสเพื่ออธิบายตัวเองที่นั่น
IgorK

1
@ คริสโตเฟอร์,? เหตุใดจึงโพสต์นี้เป็นวิกิชุมชน
Pacerier

@Pacerier ไม่รู้. เคยมีกฎที่ซับซ้อนเกี่ยวกับสิ่งต่างๆที่กลายเป็นวิกิชุมชนโดยอัตโนมัติ
Kristopher Johnson

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

47

รหัสการติดตั้งและการทดสอบเป็นสัตว์ที่แตกต่างกันและกฎการแยกตัวประกอบจะใช้แตกต่างกันไป

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

ในทางกลับกันรหัสทดสอบจะต้องรักษาระดับการทำซ้ำ การทำซ้ำในรหัสทดสอบทำให้บรรลุเป้าหมายสองประการ:

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

ฉันมักจะเพิกเฉยต่อการทำซ้ำโค้ดทดสอบเล็กน้อยตราบใดที่วิธีทดสอบแต่ละวิธียังคงสั้นกว่าประมาณ 20 บรรทัด ฉันชอบเมื่อจังหวะการตั้งค่ารัน - ตรวจสอบปรากฏในวิธีการทดสอบ

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

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

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


8

ฉันเห็นด้วย. การแลกเปลี่ยนมีอยู่ แต่แตกต่างกันในสถานที่ต่างๆ

ฉันมีแนวโน้มที่จะ refactor รหัสที่ซ้ำกันสำหรับการตั้งค่าสถานะ แต่มีโอกาสน้อยที่จะ refactor ส่วนของการทดสอบที่ใช้โค้ดจริงๆ ที่กล่าวว่าหากการใช้รหัสมักใช้รหัสหลายบรรทัดฉันอาจคิดว่านั่นเป็นกลิ่นและเปลี่ยนรหัสจริงภายใต้การทดสอบ และจะช่วยเพิ่มความสามารถในการอ่านและการบำรุงรักษาของทั้งโค้ดและการทดสอบ


ฉันคิดว่านี่เป็นความคิดที่ดี หากคุณมีการทำซ้ำจำนวนมากให้ดูว่าคุณสามารถ refactor เพื่อสร้าง "อุปกรณ์ทดสอบ" ทั่วไปที่สามารถเรียกใช้การทดสอบจำนวนมากได้หรือไม่ สิ่งนี้จะกำจัดรหัสการตั้งค่า / การฉีกขาดที่ซ้ำกัน
Outlaw Programmer

8

คุณสามารถลดการทำซ้ำโดยใช้รสชาติแตกต่างกันหลายวิธีการทดสอบยูทิลิตี้

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


6

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


3

ฉันชอบ rspec เพราะสิ่งนี้:

มี 2 ​​สิ่งที่จะช่วยได้ -

  • กลุ่มตัวอย่างที่ใช้ร่วมกันสำหรับการทดสอบพฤติกรรมทั่วไป
    คุณสามารถกำหนดชุดการทดสอบจากนั้น "รวม" ที่กำหนดไว้ในการทดสอบจริงของคุณ

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

ยิ่ง. NET / Java / กรอบการทดสอบอื่น ๆ ใช้วิธีการเหล่านี้เร็วเท่าไหร่ก็ยิ่งดี (หรือคุณสามารถใช้ IronRuby หรือ JRuby เพื่อเขียนการทดสอบของคุณซึ่งโดยส่วนตัวแล้วฉันคิดว่าเป็นตัวเลือกที่ดีกว่า)


3

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

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

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


2

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


2

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

การมีการทดสอบหน่วยให้แยกออกจากกันมากที่สุดยังช่วยให้การทดสอบมุ่งเน้นไปที่ฟังก์ชันเฉพาะที่พวกเขากำหนดเป้าหมายไว้

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


2

"ปรับโครงสร้างใหม่เพื่อให้แห้งมากขึ้น - เจตนาของการทดสอบแต่ละครั้งไม่ชัดเจนอีกต่อไป"

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

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

ในสมัยก่อนเรามีเครื่องมือทดสอบที่ใช้ภาษาโปรแกรมต่างๆ เป็นการยาก (หรือเป็นไปไม่ได้) ในการออกแบบการทดสอบที่ถูกใจและใช้งานง่าย

คุณมีพลังเต็มที่ไม่ว่าคุณจะใช้ภาษาใด - Python, Java, C # - ดังนั้นจงใช้ภาษานั้นให้ดี คุณสามารถบรรลุรหัสทดสอบที่ดูดีชัดเจนและไม่ซ้ำซ้อนเกินไป ไม่มีการแลกเปลี่ยน

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