ควรจัดรหัสทดสอบหน่วย C ++ เพื่อประสิทธิภาพการทดสอบหน่วยสูงสุดอย่างไร


47
  • คำถามนี้ไม่เกี่ยวกับกรอบการทดสอบหน่วย
  • คำถามนี้ไม่เกี่ยวกับการเขียนการทดสอบหน่วย
  • คำถามนี้เกี่ยวกับสถานที่ที่จะวางโค้ด UT และวิธี / เวลา / สถานที่ในการรวบรวมและเรียกใช้

ในการทำงานอย่างมีประสิทธิภาพด้วยรหัสมรดก Michael Feathers ยืนยันว่า

ทดสอบหน่วยที่ดี ... ทำงานเร็ว

และนั่น

การทดสอบหน่วยที่ใช้เวลา 1 / 10th ของวินาทีในการทำงานคือการทดสอบหน่วยช้า

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

เห็นได้ชัดว่าปัญหาใน C ++ คือการ "เรียกใช้" การทดสอบหน่วยของคุณคุณต้อง:

  1. แก้ไขรหัสของคุณ (การผลิตหรือการทดสอบหน่วยขึ้นอยู่กับ "รอบ" ที่คุณอยู่)
  2. รวบรวม
  3. ลิงค์
  4. เริ่มการทดสอบหน่วยปฏิบัติการ ( s )

แก้ไข(หลังจากลงคะแนนเสียงแปลก ๆ ) : ก่อนลงรายละเอียดฉันจะลองสรุปประเด็นที่นี่:

รหัสการทดสอบหน่วย C ++ จะมีการจัดระเบียบอย่างมีประสิทธิภาพได้อย่างไรเพื่อให้ทั้งสองมีประสิทธิภาพในการแก้ไขรหัส (ทดสอบ) และเรียกใช้รหัสทดสอบ?


แรกปัญหาแล้วคือการตัดสินใจที่จะใส่รหัสหน่วยทดสอบเพื่อที่:

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

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

ตัวเลือกมาก:

  • แต่ละหน่วยทดสอบทดสอบหน่วยอยู่ในไฟล์ cpp ที่แยกต่างหากและไฟล์ cpp นี้จะรวบรวม + เชื่อมโยงแยกต่างหาก (พร้อมกับไฟล์หน่วยรหัสที่มามันทดสอบ) เพื่อปฏิบัติการเดียวซึ่งจะเรียกใช้การทดสอบหน่วยนี้
    • (+) นี่ช่วยลดเวลาเริ่มต้น (รวบรวม + ลิงก์!) สำหรับหน่วยทดสอบเดียว
    • (+) การทดสอบรันเร็วมากเพราะจะทดสอบเพียงหนึ่งหน่วย
    • (-) การดำเนินการชุดทั้งหมดจะต้องเริ่มกระบวนการเป็นพันล้าน อาจเป็นปัญหาในการจัดการ
    • (-) ค่าใช้จ่ายในกระบวนการเริ่มต้นจะปรากฏให้เห็น
  • อีกด้านหนึ่งจะต้องมี - ยังคง - ไฟล์ cpp หนึ่งไฟล์ต่อการทดสอบ แต่ไฟล์ cpp ทดสอบทั้งหมด (พร้อมกับรหัสที่พวกเขาทดสอบ!) จะเชื่อมโยงกับหนึ่งไฟล์ปฏิบัติการ (ต่อโมดูล / ต่อโครงการ / เลือกตัวเลือก)
    • (+) เวลาในการคอมไพล์ยังคงเป็น OK เพราะโค้ดที่ถูกเปลี่ยนแปลงเท่านั้นที่จะคอมไพล์
    • (+) การใช้งานชุดทั้งหมดนั้นง่ายเนื่องจากมีเพียง exe ตัวเดียวที่จะเรียกใช้
    • (-) ชุดจะใช้เวลานานในการเชื่อมโยงเนื่องจากการรวบรวมใหม่ของวัตถุใด ๆ จะก่อให้เกิดการเชื่อมโยงใหม่
    • (-) (?) ชุดสูทจะใช้เวลานานกว่าถึงแม้ว่าการทดสอบหน่วยทั้งหมดจะเร็ว แต่เวลาควรจะตกลง

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

เรื่องราวในโลกแห่งความเป็นจริงและประสบการณ์ชื่นชม!

หมายเหตุ:

  • คำถามนี้ตั้งใจทิ้งแพลตฟอร์มที่ไม่ระบุและระบบ / โครงการ
  • คำถามที่ติดแท็ก UT & C ++เป็นจุดเริ่มต้นที่ดี แต่น่าเสียดายที่มีคำถามมากเกินไป
  • ไม่นานมานี้ฉันตอบคำถามคล้าย ๆ กันเกี่ยวกับโครงสร้างสำหรับการทดสอบหน่วยเพิ่ม ฉันพบว่าโครงสร้างนี้ขาดการทดสอบหน่วย "ของจริง" อย่างรวดเร็ว และฉันพบว่าคำถามอื่นแคบเกินไปดังนั้นคำถามใหม่นี้

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

3
@Closers: คุณช่วยระบุข้อโต้แย้งที่ทำให้คุณปิดคำถามนี้ได้ไหม?
Luc Touraille

ฉันไม่เห็นเลยว่าทำไมต้องปิดตัวลง :-(คุณคิดว่าจะหาคำตอบของคำถามเหล่านี้ที่ไหนถ้าไม่มีในฟอรัมนี้

2
@ โจ: ทำไมฉันต้องมีการทดสอบหน่วยเพื่อค้นหาว่ามีบางอย่างจะคอมไพล์เมื่อคอมไพเลอร์จะบอกฉันหรือไม่?
David Thornley

2
@ David: เพราะคุณต้องการที่จะให้แน่ใจว่ามันไม่ได้รวบรวม ตัวอย่างที่รวดเร็วจะเป็นวัตถุไปป์ไลน์ที่รับอินพุตและและผลิตเอาต์พุตPipeline<A,B>.connect(Pipeline<B,C>)ควรรวบรวมในขณะที่Pipeline<A,B>.connect(Pipeline<C,D>)ไม่ควรคอมไพล์: ประเภทเอาต์พุตของสเตจแรกไม่เข้ากันกับประเภทอินพุตของสเตจที่สอง
sebastiangeiger

คำตอบ:


6

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


6

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

ฉันชอบที่จะนำการทดสอบหน่วยในต้นไม้ไดเรกทอรีที่เงาต้นไม้หลัก ถ้าผมมี/sources/componentA/alpha/foo.ccและ/objects/componentA/beta/foo.oผมก็ต้องการสิ่งที่ต้องการและ/UTest_sources/componentA/alpha/test_foo.cc /UTest_objects/componentA/beta/test_foo.oฉันใช้เงาต้นไม้เดียวกันสำหรับวัตถุต้นขั้ว / จำลองและแหล่งอื่น ๆ ที่การทดสอบต้องการ จะมีบางกรณีขอบ แต่โครงการนี้ช่วยลดความยุ่งยากมาก มาโครตัวแก้ไขที่ดีสามารถดึงแหล่งทดสอบขึ้นข้างแหล่งกำเนิดวัตถุได้อย่างง่ายดาย ระบบ build ที่ดี (เช่น GNUMake) สามารถรวบรวมได้ทั้งสองและรันการทดสอบด้วยคำสั่งเดียว (เช่นmake test_foo) และสามารถจัดการกระบวนการดังกล่าวได้หลายพันล้านล้าน - เพียงแค่แหล่งที่มีการเปลี่ยนแปลงตั้งแต่พวกเขาผ่านการทดสอบครั้งล่าสุด - ค่อนข้างง่าย (ฉันมี ไม่เคยพบค่าใช้จ่ายในการเริ่มต้นกระบวนการเหล่านี้เป็นปัญหา แต่เป็น O (N)

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


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