คุณจะทำให้การทดสอบของคุณทำงานได้อย่างมีประสิทธิภาพขณะที่คุณออกแบบใหม่


14

codebase ที่ผ่านการทดสอบเป็นอย่างดีมีประโยชน์หลายประการ แต่การทดสอบบางแง่มุมของระบบจะส่งผลให้เป็น codebase ที่ทนทานต่อการเปลี่ยนแปลงบางประเภท

ตัวอย่างคือการทดสอบผลลัพธ์เฉพาะ - เช่นข้อความหรือ HTML การทดสอบมักจะเขียน (ไร้เดียงสา?) เพื่อคาดหวังว่าจะมีบล็อกข้อความที่เฉพาะเจาะจงเป็นเอาต์พุตสำหรับพารามิเตอร์ป้อนเข้าบางส่วนหรือเพื่อค้นหาส่วนเฉพาะในบล็อก

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

  • คุณจะจัดการงานในการค้นหาและเขียนการทดสอบเหล่านี้ใหม่ได้อย่างไร? จะทำอย่างไรถ้าคุณไม่สามารถ "เรียกใช้" ทั้งหมดและปล่อยให้กรอบเรียงลำดับออก "

  • ผลการทดสอบภายใต้รหัสอื่น ๆ ประเภทใดในการทดสอบที่เปราะบางเป็นปกติ


สิ่งนี้แตกต่างจากprogrammers.stackexchange.com/questions/5898/อย่างไร?
AShelly

4
คำถามนั้นถูกถามผิดเกี่ยวกับการปรับโครงสร้าง - การทดสอบหน่วยควรคงที่ภายใต้การปรับสภาพใหม่
Alex Feinman

คำตอบ:


9

ฉันรู้ว่าคน TDD จะเกลียดคำตอบนี้ แต่ส่วนใหญ่สำหรับฉันคือการเลือกอย่างระมัดระวังว่าจะทดสอบอะไร

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

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

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

ไม่ได้หมายความว่าคุณยังไม่สามารถระบุคุณลักษณะและรหัสเดียวได้จนกว่าคุณสมบัตินั้นจะตรงตามเกณฑ์การยอมรับ หมายความว่าในบางกรณีคุณไม่ได้สิ้นสุดการวัดเกณฑ์การยอมรับด้วยการทดสอบหน่วย


ฉันคิดว่าคุณตั้งใจจะเขียน "นอกโมดูล" ไม่ใช่ "นอกแอพ"
SamB

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

ฉันพบว่าวิธีนี้เข้ากันได้กับ TDD มาก ฉันชอบเริ่มต้นในเลเยอร์ด้านบนของแอปพลิเคชันใกล้กับผู้ใช้เพื่อให้ฉันสามารถออกแบบเลเยอร์ที่ต่ำกว่าที่รู้ว่าเลเยอร์ด้านบนจำเป็นต้องใช้เลเยอร์ที่ต่ำกว่าได้อย่างไร การสร้างจากบนลงล่างเป็นหลักช่วยให้คุณออกแบบอินเตอร์เฟสได้อย่างแม่นยำมากขึ้น
Greg Burghardt

4

ฉันเพิ่งเสร็จสิ้นการยกเครื่องครั้งใหญ่ของ SIP stack ของฉันใหม่เขียนการขนส่ง TCP ทั้งหมด (นี่เป็น refactor ใกล้ ๆ ในระดับที่ค่อนข้างใหญ่เมื่อเทียบกับ refactorings ส่วนใหญ่)

โดยย่อมี TIdSipTcpTransport ซึ่งเป็นคลาสย่อยของ TIdSipTransport TIdSipTransports ทั้งหมดใช้ชุดทดสอบร่วมกัน Internal to TIdSipTcpTransport มีจำนวนคลาส - แผนที่ที่มีการเชื่อมต่อ / การเริ่มต้น - ข้อความคู่ไคลเอนต์ TCP เธรดเซิร์ฟเวอร์ TCP เธรดและอื่น ๆ

นี่คือสิ่งที่ฉันทำ:

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

ฉันรู้ว่าสิ่งที่ฉันยังต้องทำในรูปแบบของการทดสอบความคิดเห็น (*) และรู้ว่ารหัสใหม่ทำงานได้ตามที่คาดหวังขอบคุณการทดสอบใหม่ที่ฉันเขียน

(*) จริง ๆ คุณไม่จำเป็นต้องแสดงความคิดเห็น อย่าเพิ่งวิ่งหนี การทดสอบที่ล้มเหลว 100 ข้อไม่ได้รับการสนับสนุนมากนัก นอกจากนี้ในการตั้งค่าของฉันโดยเฉพาะการรวบรวมการทดสอบที่น้อยลงหมายถึงการวนการทดสอบ - เขียน - refactor ที่เร็วขึ้น


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

3

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

คุณสามารถ:

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

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


มันอาจช่วยในการใช้ HTML โครงสร้าง
SamB

@ SamB แน่นอนว่าจะช่วยได้ แต่ฉันไม่คิดว่ามันจะแก้ปัญหาได้อย่างสมบูรณ์
Winston Ewert

ไม่แน่นอนไม่มีอะไรที่สามารถทำได้ :-)
SamB

-1

ก่อนอื่นให้สร้าง API ใหม่นั่นคือสิ่งที่คุณต้องการให้พฤติกรรม API ใหม่ของคุณเป็น หากเกิดขึ้นว่า API ใหม่นี้มีชื่อเหมือนกับ OLDER API ฉันจะผนวกชื่อ _NEW ต่อท้ายชื่อ API ใหม่

int DoSomethingInterestingAPI ();

กลายเป็น:

int DoSomethingInterestingAPI_NEW (int takes_more_arguments); int DoSomethingInterestingAPI_OLD (); int DoSomethingInterestingAPI () {DoSomethingInterestingAPI_NEW (Anything_default_mimics_the_old_API); ตกลง - ในขั้นตอนนี้ - การทดสอบการถดถอยทั้งหมดของคุณผ่านบัตรผ่าน - ใช้ชื่อ DoSomethingInterestingAPI ()

ถัดไปอ่านรหัสของคุณและเปลี่ยนการโทรทั้งหมดเป็น DoSomethingInterestingAPI () เป็นตัวแปรที่เหมาะสมของ DoSomethingInterestingAPI_NEW () ซึ่งรวมถึงการอัปเดต / เขียนใหม่ส่วนใดของการทดสอบการถดถอยของคุณจะต้องเปลี่ยนเพื่อใช้ API ใหม่

ถัดไปทำเครื่องหมาย DoSomethingInterestingAPI_OLD () เป็น [[คัดค้าน ()]] เก็บ API ที่เลิกใช้ไว้นานเท่าที่คุณต้องการ (จนกว่าคุณจะได้รับการอัปเดตรหัสทั้งหมดอย่างปลอดภัยที่อาจต้องพึ่งพา)

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

นี่เป็นตัวอย่างที่ดี (ยาก) ของแนวทางนี้ในทางปฏิบัติ ฉันมีฟังก์ชัน BitSubstring () - ที่ฉันใช้วิธีการที่มีพารามิเตอร์ที่สามเป็น COUNT บิตใน substring เพื่อให้สอดคล้องกับ API และรูปแบบอื่น ๆ ใน C ++ ฉันต้องการเปลี่ยนเป็นเริ่มต้น / สิ้นสุดเป็นอาร์กิวเมนต์ของฟังก์ชัน

https://github.com/SophistSolutions/Stroika/commit/003dd8707405c43e735ca71116c773b108c217c0

ฉันสร้างฟังก์ชัน BitSubstring_NEW ด้วย API ใหม่และอัปเดตโค้ดทั้งหมดของฉันเพื่อใช้งานนั้น (ไม่ต้องเรียกอีกต่อไปเป็น BitSubString) แต่ฉันออกจากการใช้งานสำหรับการเปิดตัวหลายครั้ง (เดือน) - และทำเครื่องหมายว่าเลิกใช้แล้ว - เพื่อให้ทุกคนสามารถเปลี่ยนเป็น BitSubString_NEW (และในเวลานั้นเปลี่ยนอาร์กิวเมนต์จากการนับเป็นรูปแบบเริ่มต้น / สิ้นสุด)

จากนั้น - เมื่อการเปลี่ยนแปลงดังกล่าวเสร็จสมบูรณ์ฉันได้กระทำการลบ BitSubString () อีกครั้งและเปลี่ยนชื่อ BitSubString_NEW-> BitSubString () (และเลิกใช้ชื่อ BitSubString_NEW)


อย่าผนวกส่วนต่อท้ายที่ไม่มีความหมายหรือไม่เห็นด้วยกับชื่อ พยายามให้ชื่อที่มีความหมายเสมอ
Basilevs

คุณพลาดจุดไปโดยสิ้นเชิง ก่อน - สิ่งเหล่านี้ไม่ใช่คำต่อท้ายที่ "ไม่มีความหมาย" พวกเขามีความหมายว่า API กำลังเปลี่ยนจากอันเก่าไปเป็นอันใหม่ ในความเป็นจริงนั่นคือประเด็นทั้งหมดของคำถามที่ฉันตอบไปและประเด็นทั้งหมดของคำตอบ ชื่อสื่อสารอย่างชัดเจนซึ่งเป็น OLD API ซึ่งเป็น NEW API และเป็นชื่อเป้าหมายสุดท้ายของ API เมื่อการเปลี่ยนแปลงเสร็จสมบูรณ์ และ - ส่วนต่อท้าย _OLD / _NEW เป็นแบบชั่วคราว - เฉพาะในช่วงการเปลี่ยนการเปลี่ยนแปลง API
Lewis Pringle

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