ฉันจะสนับสนุนการทดสอบหน่วยในรหัสส่วนตัวได้อย่างไร


15

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

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


3
หากคุณมีวิธีการส่วนตัวที่คุณรู้สึกว่าจำเป็นต้องทดสอบนั่นเป็นสัญญาณบ่งบอกว่ารหัสของคุณละเมิด SRP และมีอีกคลาสหนึ่งที่ร้องออกมาเพื่อสกัดและทดสอบด้วยตนเอง
Paddyslacker

@ Paddyslacker: ฉันรู้สึกว่ารหัสทั้งหมดจะต้องมีการทดสอบ ผมไม่เห็นว่าทำไมหน่วยรหัสที่เป็นไปตามหลักการรับผิดชอบเดียวไม่ควรอยู่ภายใต้การทดสอบหน่วย ...
Wizard79

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

@ Paddyslacker: ฉันรู้สึกว่าต้องทดสอบวิธีส่วนตัวโดยตรงด้วย ทำไมคุณคิดว่าพวกเขาไม่ควรเป็นส่วนตัว
Wizard79

6
ด้วยการทดสอบวิธีการส่วนตัวที่ทำให้คุณเกิดสิ่งที่เป็นนามธรรม คุณควรจะทดสอบสถานะและ / หรือพฤติกรรมที่ไม่ใช่การนำไปใช้ในการทดสอบหน่วย ตัวอย่าง / สถานการณ์ของคุณควรจะสามารถตรวจสอบได้ว่าผลลัพธ์ของรหัสส่วนตัวนั้นเป็นอย่างไร - หากคุณพบว่ามันยากดังนั้น Paddyslacker ก็บอกว่ามันอาจหมายความว่าคุณกำลังละเมิด SRP นอกจากนี้ยังอาจหมายความว่าคุณไม่ได้กลั่นตัวอย่างเพื่อเป็นตัวแทนของรหัสของคุณอย่างแท้จริง
FinnNk

คำตอบ:


9

เพื่อนร่วมงานของคุณอาจทำให้การทดสอบหน่วยจริงสับสนด้วยการทดสอบการรวม หากผลิตภัณฑ์ของคุณคือ (หรือมี) API การทดสอบการรวมสามารถตั้งโปรแกรมเป็นกรณีทดสอบ NUnit บางคนเชื่อผิดว่าเป็นการทดสอบหน่วย

คุณสามารถลองโน้มน้าวเพื่อนร่วมงานของคุณด้วยสิ่งต่อไปนี้ (ฉันแน่ใจว่าคุณรู้เรื่องนี้แล้วสิ่งที่ฉันพูดคือการชี้ให้เพื่อนร่วมงานของคุณเห็นอาจช่วยได้):

  • ครอบคลุมการทดสอบ วัดเปอร์เซ็นต์ความครอบคลุมการทดสอบจริงของการทดสอบการรวมเหล่านั้น นี่คือการตรวจสอบความเป็นจริงสำหรับผู้ที่ไม่เคยเรียกใช้การครอบคลุมการทดสอบ เนื่องจากเป็นการยากที่จะออกกำลังกายเส้นทางแบบลอจิคัลทั้งหมดเมื่ออินพุตอยู่ห่างออกไปหลายเลเยอร์การครอบคลุมการทดสอบจึงอยู่ที่ใดที่หนึ่งระหว่าง 20% ถึง 50% เพื่อให้ครอบคลุมมากขึ้นเพื่อนร่วมงานของคุณจำเป็นต้องเขียนการทดสอบหน่วยที่แยกได้จริง
  • องค์ประกอบ ปรับใช้ซอฟต์แวร์เดียวกันภายใต้การทดสอบและบางทีคุณสามารถแสดงให้เพื่อนร่วมงานเห็นว่าการรันการทดสอบในสภาพแวดล้อมที่แตกต่างกันนั้นเป็นเรื่องยากเพียงใด พา ธ ไปยังไฟล์ต่าง ๆ สตริงการเชื่อมต่อฐานข้อมูล URL ของบริการระยะไกลและอื่น ๆ ทั้งหมดเพิ่มขึ้น
  • เวลาดำเนินการ เว้นแต่การทดสอบเป็นการทดสอบหน่วยจริงและสามารถทำงานในหน่วยความจำได้พวกเขาจะใช้เวลานานในการรัน

12

เหตุผลในการใช้การทดสอบหน่วยในรหัสภายใน / ส่วนตัวนั้นเหมือนกับของ API ที่ได้รับการสนับสนุนจากภายนอก:

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

8

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

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

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

ต่อไปนี้เป็นตัวอย่างที่ไร้สาระแน่นอนในชีวิตจริงคุณจะต้องทำการทดสอบมากกว่านี้:

สมมติว่าคุณมีคลาสที่ส่งคืนรายการที่เรียงลำดับของสตริง - คุณควรตรวจสอบว่าผลลัพธ์ถูกเรียงลำดับไม่ใช่วิธีที่เรียงลำดับรายการนั้นจริง ๆ คุณสามารถเริ่มต้นการนำไปใช้งานของคุณด้วยอัลกอริทึมเดียวที่เพียงเรียงลำดับรายการ เมื่อเสร็จสิ้นการทดสอบของคุณไม่จำเป็นต้องเปลี่ยนถ้าคุณเปลี่ยนอัลกอริทึมการเรียงลำดับของคุณ ณ จุดนี้คุณมีการทดสอบเดียว (สมมติว่าการเรียงลำดับถูกฝังอยู่ในคลาสของคุณ):

  1. ผลลัพธ์ของฉันถูกจัดเรียงหรือไม่

ทีนี้พูดว่าคุณต้องการอัลกอริธึมสองอัน (บางทีอาจจะมีประสิทธิภาพมากกว่าในบางสถานการณ์ แต่ไม่ใช่อัลกอริธึมอื่น ๆ ) จากนั้นแต่ละอัลกอริทึมสามารถทำได้ (และโดยทั่วไปควร) โดยชั้นเรียนอื่น สถานการณ์ที่คุณเลือกโดยใช้ mocks แต่การทดสอบดั้งเดิมของคุณยังคงใช้ได้และเนื่องจากเรากำลังตรวจสอบพฤติกรรม / สถานะที่สังเกตได้เท่านั้นจึงไม่จำเป็นต้องเปลี่ยน คุณจบด้วยการทดสอบ 3 ครั้ง:

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

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

การทดสอบ (ประเภทนี้) ควรเปลี่ยนเฉพาะเมื่อมีการเปลี่ยนแปลงพฤติกรรมเริ่มเขียนการทดสอบกับรหัสส่วนตัวและออกไปนอกหน้าต่าง


1
อาจมีความเข้าใจผิดเกี่ยวกับความหมายของ "ส่วนตัว" ในระบบของเรา 99% ของรหัสคือ "ส่วนตัว" จากนั้นเรามี API ขนาดเล็กสำหรับอัตโนมัติ / การควบคุมระยะไกลหนึ่งในองค์ประกอบของระบบ ฉันหมายถึงหน่วยทดสอบรหัสของโมดูลอื่นทั้งหมด
Wizard79

4

นี่เป็นอีกเหตุผลหนึ่ง: ในกรณีสมมุติฉันต้องเลือกระหว่างการทดสอบหน่วย API ภายนอกกับชิ้นส่วนส่วนตัวฉันจะเลือกชิ้นส่วนส่วนตัว

หากทุกส่วนส่วนตัวถูกปกคลุมด้วยการทดสอบ API ที่ประกอบด้วยส่วนส่วนตัวเหล่านี้ควรได้รับการคุ้มครองเกือบ 100% เช่นกันยกเว้นเพียงแค่ชั้นบน แต่นั่นก็น่าจะเป็นชั้นบาง ๆ

ในทางกลับกันเมื่อทดสอบ API เท่านั้นมันอาจเป็นเรื่องยากที่จะครอบคลุมเส้นทางโค้ดที่เป็นไปได้ทั้งหมด


+1 "ในทางกลับกัน ... " แต่ถ้าไม่มีอะไรเพิ่มการทดสอบที่ความล้มเหลวจะทำร้ายมากที่สุด
Tony Ennis

2

เป็นการยากที่จะให้ผู้คนยอมรับการทดสอบหน่วยเพราะดูเหมือนว่าเป็นการเสียเวลา ("เราอาจทำโครงการทำเงินอีก!") หรือเรียกซ้ำ ("จากนั้นเราต้องเขียนกรณีทดสอบสำหรับกรณีทดสอบ!") ฉันมีความผิดในการพูดทั้งสองอย่าง

ครั้งแรกที่คุณพบข้อผิดพลาดคุณต้องเผชิญกับความจริงที่ว่าคุณไม่สมบูรณ์แบบ (เราจำได้เร็วแค่ไหนโปรแกรมเมอร์!) และคุณไป "อืมม."


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


คุณถามผู้ร่วมงานของคุณว่าเพราะเหตุใดการทดสอบหน่วยจึงมีประโยชน์สำหรับ API ที่พบกับภายนอกเท่านั้น


วิธีหนึ่งในการแสดงค่าของการทดสอบหน่วยคือการรอให้บั๊กที่น่ารังเกียจเกิดขึ้นแล้วแสดงว่าการทดสอบหน่วยสามารถป้องกันได้อย่างไร นั่นไม่ใช่การถูหน้าพวกเขาในใจของพวกเขาย้ายการทดสอบยูนิตจากทฤษฎีหอคอยงาช้างไปตามความเป็นจริงในสนามเพลาะ

อีกวิธีหนึ่งคือการรอจนกว่าข้อผิดพลาดเดียวกันเกิดขึ้นสองครั้ง "อืมเจ้านายดีเราได้เพิ่มรหัสเพื่อทดสอบว่างหลังจากปัญหาของสัปดาห์ที่แล้ว แต่ผู้ใช้ป้อนสิ่งที่ว่างเปล่าครั้งนี้!"


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


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


2

รหัสเอกชนที่ได้รับการเรียกตามรหัสของประชาชน (หรือรหัสเอกชนที่ได้รับการเรียกตามรหัสเอกชนที่ได้รับการเรียกตามรหัสของประชาชน (หรือ ... )) และรหัสส่วนตัวว่ามีสองชนิดของรหัสส่วนตัวไม่ได้ในที่สุดได้รับการเรียกโดยสาธารณชน รหัส.

อดีตได้รับการทดสอบผ่านการทดสอบสำหรับรหัสสาธารณะ ไม่สามารถเรียกหลังได้เลยจึงควรลบไม่ใช่ทดสอบ

โปรดทราบว่าเมื่อคุณทำ TDD จะไม่สามารถมีรหัสส่วนตัวที่ยังไม่ผ่านการทดสอบได้


ในระบบของเรา 99% ของรหัสเป็นประเภทที่สาม : ส่วนตัวไม่ได้เรียกโดยรหัสสาธารณะและจำเป็นสำหรับระบบ (เพียงส่วนน้อยที่สุดของระบบของเรามี API สาธารณะภายนอก)
Wizard79

1
"โปรดทราบว่าเมื่อคุณทำ TDD จะไม่สามารถมีรหัสส่วนตัวที่ยังไม่ทดลอง" <- ลบกรณีทดสอบโดยไม่ทราบว่าการทดสอบนั้นเป็นการทดสอบเพียงอย่างเดียวเพื่อครอบคลุมสาขาเฉพาะ ตกลงนั่นคือรหัส "ที่ยังไม่ได้ทดสอบในปัจจุบัน" มากกว่านี้ แต่ง่ายพอที่จะเห็นการเปลี่ยนโครงสร้างเล็กน้อยในภายหลังเปลี่ยนรหัสนั้น ... เฉพาะชุดทดสอบของคุณเท่านั้นที่ไม่ครอบคลุมอีกต่อไป
Frank Shearar

2

การทดสอบหน่วยคือทั้งหมดที่เกี่ยวกับการทดสอบหน่วยของรหัสของคุณ มันขึ้นอยู่กับคุณที่จะกำหนดว่าหน่วยคืออะไร เพื่อนร่วมงานของคุณกำหนดหน่วยเป็นองค์ประกอบ API

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

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

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