ฉันจะทดสอบฟังก์ชั่นส่วนตัวหรือชั้นเรียนที่มีวิธีการส่วนตัวเขตข้อมูลหรือชั้นในได้อย่างไร


2727

ฉันจะทดสอบหน่วย (โดยใช้ xUnit) คลาสที่มีวิธีการส่วนตัวภายในเขตข้อมูลหรือคลาสที่ซ้อนกันได้อย่างไร หรือฟังก์ชั่นที่ทำให้เป็นส่วนตัวโดยมีการเชื่อมโยงภายใน ( staticใน C / C ++) หรืออยู่ในเนมสเปซส่วนตัว ( ไม่ระบุชื่อ )?

ดูเหมือนว่าการเปลี่ยนแปลงตัวปรับการเข้าถึงสำหรับวิธีการหรือฟังก์ชั่นจะไม่สามารถทำการทดสอบได้


257
วิธีที่ดีที่สุดในการทดสอบวิธีการส่วนตัวไม่ได้ทดสอบโดยตรง
Surya


383
ฉันไม่เห็นด้วย. วิธีการ (สาธารณะ) ซึ่งยาวหรือยากที่จะเข้าใจจะต้องมีการปรับโครงสร้าง มันจะเป็นการโง่ที่จะไม่ทดสอบวิธีเล็ก ๆ (ส่วนตัว) ที่คุณได้รับแทนที่จะเป็นแค่วิธีสาธารณะ
Michael Piefel

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

126
คุณจะต้องทดสอบการเรียนการทำงานไม่ได้ของการดำเนินงาน ต้องการทดสอบวิธีการส่วนตัวหรือไม่ ทดสอบวิธีสาธารณะที่เรียกพวกเขา หากฟังก์ชั่นการเรียนที่เสนอนั้นได้รับการทดสอบอย่างละเอียดภายในของมันแสดงให้เห็นว่าถูกต้องและเชื่อถือได้ คุณไม่จำเป็นต้องทดสอบเงื่อนไขภายใน การทดสอบควรรักษา decoupling จากคลาสที่ทดสอบ
Calimar41

คำตอบ:


1640

ปรับปรุง:

10 ปีต่อมาบางทีวิธีที่ดีที่สุดในการทดสอบวิธีการส่วนตัวหรือสมาชิกที่ไม่สามารถเข้าถึงได้นั้น@JailbreakมาจากกรอบของManifold

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

วิธีนี้รหัสของคุณยังคงปลอดภัยและสามารถอ่านได้ ไม่มีการออกแบบที่ประนีประนอมไม่มีวิธีการและการเปิดเผยที่มากเกินไปสำหรับการทดสอบ

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

ภายในเราใช้ผู้ช่วยในการรับ / ตั้งค่าprivateและprivate staticตัวแปรรวมถึงการเรียกใช้privateและprivate staticวิธีการ รูปแบบต่อไปนี้จะช่วยให้คุณสามารถทำสิ่งใดก็ได้ที่เกี่ยวข้องกับวิธีการและฟิลด์ส่วนตัว แน่นอนคุณไม่สามารถเปลี่ยนprivate static finalตัวแปรผ่านการสะท้อนกลับได้

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

และสำหรับเขตข้อมูล:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

หมายเหตุ:
1. TargetClass.getDeclaredMethod(methodName, argClasses)ให้คุณดูprivateวิธีการ getDeclaredFieldสิ่งเดียวกันที่ใช้สำหรับ
2. setAccessible(true)จะต้องเล่นรอบกับเอกชน


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

21
มีประโยชน์มาก. การใช้สิ่งนี้เป็นสิ่งสำคัญที่จะต้องจำไว้ว่ามันจะล้มเหลวไม่ดีหากการทดสอบถูกเรียกใช้ obfuscation โพสต์
Rick Minerich

20
โค้ดตัวอย่างใช้งานไม่ได้สำหรับฉัน แต่สิ่งนี้ทำให้ thigs ชัดเจนยิ่งขึ้น: java2s.com/Tutorial/Java/0125__Reflection/…
Rob

35
ดีกว่าใช้สะท้อนโดยตรงจะใช้ห้องสมุดบางอย่างสำหรับมันเช่นPowermock
Michael Piefel

25
นี่คือเหตุผลที่การทดสอบการออกแบบขับเคลื่อนมีประโยชน์ ช่วยให้คุณทราบว่าต้องเปิดเผยอะไรบ้างเพื่อตรวจสอบพฤติกรรม
Thorbjørn Ravn Andersen

621

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

  1. วิธีการส่วนตัวคือรหัสที่ตายแล้ว
  2. มีกลิ่นการออกแบบใกล้กับชั้นเรียนที่คุณกำลังทดสอบ
  3. วิธีที่คุณพยายามทดสอบไม่ควรเป็นแบบส่วนตัว

6
นอกจากนี้เราไม่เคยได้รับความครอบคลุมโค้ดเกือบ 100% อยู่แล้วดังนั้นทำไมไม่ลองใช้เวลาทดสอบคุณภาพกับวิธีการที่ลูกค้าจะใช้โดยตรง
กรินช์

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

9
@ AlexWien คุณถูก ความคุ้มครองไม่ใช่คำที่ถูกต้อง สิ่งที่ฉันควรพูดคือ "ถ้าการทดสอบสัญญาของคุณครอบคลุมเนื้อหาที่มีความหมายและรัฐที่มีความหมายทั้งหมด" คุณชี้ให้เห็นอย่างถูกต้องอาจพิสูจน์ว่าเป็นไปไม่ได้ที่จะทดสอบผ่านส่วนต่อประสานสาธารณะกับรหัสหรือทางอ้อมตามที่คุณอ้างถึง หากเป็นกรณีสำหรับรหัสที่คุณกำลังทดสอบฉันจะมีแนวโน้มที่จะคิดว่า: (1) สัญญามีความใจกว้างเกินไป (เช่นพารามิเตอร์มากเกินไปในวิธีการ) หรือ (2) สัญญานั้นคลุมเครือเกินไป (เช่นพฤติกรรมของวิธีการ แตกต่างกันอย่างมากกับสถานะคลาส) ฉันจะพิจารณาทั้งกลิ่นการออกแบบ
Erik Madsen

16
ไม่ควรทดสอบวิธีส่วนตัวเกือบทั้งหมดโดยตรง (เพื่อลดค่าใช้จ่ายในการบำรุงรักษา) ข้อยกเว้น: วิธีการทางวิทยาศาสตร์อาจมีรูปแบบเช่น f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) โดยที่ a, b, c, d, e และ f เป็นการแสดงออกที่ซับซ้อนอย่างน่ากลัว แต่ก็ไม่มีประโยชน์นอกสูตรนี้ ฟังก์ชั่นบางอย่าง (คิดว่า MD5) นั้นยากที่จะกลับด้านดังนั้นมันจึงเป็นเรื่องยาก (NP-Hard) ในการเลือกพารามิเตอร์ที่จะครอบคลุมพื้นที่พฤติกรรมอย่างสมบูรณ์ ง่ายต่อการทดสอบ a, b, c, d, e, f อย่างอิสระ ทำให้พวกเขาเป็นที่สาธารณะหรือเป็นแพคเกจส่วนตัวว่าพวกเขาสามารถนำมาใช้ซ้ำได้ การแก้ไข: ทำให้พวกเขาเป็นส่วนตัว แต่ทดสอบพวกเขา
Eponymous

4
@Eponymous ฉันฉีกขาดในนี้ คนเจ้าระเบียบเกี่ยวกับวัตถุในฉันจะบอกว่าคุณควรใช้วิธีการที่มุ่งเน้นวัตถุมากกว่าวิธีส่วนตัวคงที่ช่วยให้คุณสามารถทดสอบผ่านวิธีการสาธารณะเช่น แต่จากนั้นอีกครั้งในค่าใช้จ่ายหน่วยความจำกรอบทางวิทยาศาสตร์มีแนวโน้มที่จะกังวลทั้งหมดซึ่งฉันเชื่อว่าโต้แย้งสำหรับวิธีคงที่
Erik Madsen

323

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

วิธีการตามปกติของฉันในการจัดการกับปัญหาดังกล่าวคือการแซวคลาสใหม่ที่มีบิตที่น่าสนใจ บ่อยครั้งที่วิธีการนี้และฟิลด์ที่โต้ตอบกับและอาจแยกวิธีอื่นหรือสองวิธีในคลาสใหม่

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

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


24
คำถามคือวิธีทดสอบวิธีส่วนตัว คุณบอกว่าคุณจะทำคลาสใหม่สำหรับ (และเพิ่มความซับซ้อนมากขึ้น) และหลังจากแนะนำให้ไม่สร้างคลาสใหม่ ดังนั้นวิธีทดสอบวิธีส่วนตัว?
Dainius

27
@Dainius: ฉันไม่แนะนำให้สร้างคลาสใหม่เพียงอย่างเดียวเพื่อให้คุณสามารถทดสอบวิธีการนั้นได้ ฉันแนะนำว่าการทดสอบการเขียนสามารถช่วยคุณปรับปรุงการออกแบบของคุณ: การออกแบบที่ดีนั้นง่ายต่อการทดสอบ
Jay Bazuzi

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

6
@Danius: การเพิ่มคลาสใหม่โดยส่วนใหญ่จะลดความซับซ้อน เพียงแค่วัด การทดสอบในบางกรณีจะต่อสู้กับ OOD วิธีการที่ทันสมัยเป็นที่นิยมทดสอบได้
AlexWien

5
นี่คือว่าวิธีการใช้งานข้อเสนอแนะจากการทดสอบของคุณไดรฟ์และปรับปรุงการออกแบบของคุณ! ฟังการทดสอบของคุณ หากพวกเขายากที่จะเขียนนั่นคือการบอกคุณบางอย่างที่สำคัญเกี่ยวกับรหัสของคุณ!
pnschofield

289

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

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

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


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

10
@ Colin M นั่นไม่ใช่สิ่งที่เขาถามจริง ๆ ;) ให้เขาตัดสินใจคุณไม่ทราบว่าโครงการ
Aaron Marcus

2
ไม่จริงเลย อาจต้องใช้ความพยายามอย่างมากในการทดสอบส่วนเล็ก ๆ ของวิธีส่วนตัวโดยใช้วิธีสาธารณะ วิธีสาธารณะอาจต้องมีการตั้งค่าที่สำคัญก่อนที่จะถึงสายที่เรียกวิธีการส่วนตัวของคุณ
ACV

1
ฉันเห็นด้วย. คุณควรทดสอบหากทำสัญญาเสร็จ คุณไม่ควรทดสอบวิธีนี้จะทำ หากผู้ติดต่อสำเร็จโดยไม่ครอบคลุมรหัส 100% (ในวิธีส่วนตัว) นั่นอาจเป็นรหัสที่ไม่ทำงานหรือไร้ประโยชน์
wuppi

207

จากบทความนี้: การทดสอบวิธีการส่วนตัวกับ JUnit และ SuiteRunner (Bill Venners) คุณโดยทั่วไปมี 4 ตัวเลือก:

  1. อย่าทดสอบวิธีการส่วนตัว
  2. ให้วิธีการเข้าถึงแพ็คเกจ
  3. ใช้คลาสทดสอบที่ซ้อนกัน
  4. ใช้การสะท้อนกลับ

@JimmyT. ขึ้นอยู่กับว่า "รหัสการผลิต" มีไว้เพื่อใคร ฉันจะเรียกรหัสที่ผลิตขึ้นสำหรับแอปพลิเคชันที่ประจำอยู่ภายใน VPN ซึ่งผู้ใช้เป้าหมายคือ sysadmins เพื่อเป็นรหัสการผลิต
Pacerier

@Pacerier คุณหมายถึงอะไร
Jimmy T.

1
ตัวเลือกที่ 5 เช่นที่กล่าวถึงข้างต้นกำลังทดสอบวิธีสาธารณะที่เรียกใช้วิธีส่วนตัวหรือไม่ แก้ไข?
user2441441

1
@ LorenzoLerate ฉันได้ต่อสู้กับสิ่งนี้เพราะฉันกำลังทำการพัฒนาแบบฝังและฉันต้องการซอฟต์แวร์ของฉันที่เล็กที่สุด ข้อ จำกัด ด้านขนาดและประสิทธิภาพเป็นเหตุผลเดียวที่ฉันนึกได้

ฉันชอบการทดสอบโดยใช้การสะท้อน: นี่คือรูปแบบวิธีเมธอด = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (จริง); return method.invoke (targetObject, argObjects);
Aguid

127

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


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

14
ดังนั้นอย่าเขียนวิธีส่วนตัวเพียงแค่สร้างชั้นเรียนขนาดเล็กอื่น ๆ อีก 10 ชั้น?
มีดโกน

1
ไม่มันหมายความว่าคุณสามารถทดสอบการทำงานของวิธีการส่วนตัวโดยใช้วิธีสาธารณะที่เรียกใช้งาน
Akshat Sharda

66

เพียงสองตัวอย่างที่ฉันต้องการทดสอบวิธีการส่วนตัว:

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

ฉันเข้าใจแนวคิดของการทดสอบเฉพาะ "สัญญา" แต่ฉันไม่เห็นมีใครสามารถสนับสนุนจริง ๆ ไม่ทดสอบรหัส - ระยะของคุณอาจแตกต่างกันไป

ดังนั้นการแลกเปลี่ยนของฉันเกี่ยวข้องกับการทำให้ JUnits เกิดความสับสนมากกว่าจะส่งผลต่อความปลอดภัยและ SDK ของฉัน


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

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

1
@ngreen true ขอบคุณ - ฉันขี้เกียจกับคำว่า "สาธารณะ" ฉันได้อัปเดตคำตอบเพื่อรวมสาธารณะการป้องกันค่าเริ่มต้น (และเพื่อพูดถึงการสะท้อนกลับ) ประเด็นที่ฉันพยายามทำคือมีเหตุผลที่ดีที่รหัสบางอย่างจะเป็นความลับ แต่นั่นไม่ควรป้องกันเราจากการทดสอบ
Richard Le Mesurier

2
ขอบคุณที่ให้ตัวอย่างที่แท้จริงให้กับทุกคน คำตอบส่วนใหญ่ดี แต่ในมุมมองทางทฤษฎี ในโลกแห่งความเป็นจริงเราจำเป็นต้องซ่อนการใช้งานจากสัญญาและเรายังต้องทดสอบ การอ่านทั้งหมดนี้ฉันคิดว่านี่น่าจะเป็นการทดสอบในระดับใหม่โดยใช้ชื่ออื่น
Z. Khullah

1
อีกตัวอย่างหนึ่ง - ตัวแยกวิเคราะห์ HTML ของตัวรวบรวมข้อมูล การแยกวิเคราะห์หน้า html ทั้งหมดลงในวัตถุโดเมนต้องใช้ตรรกะ nitty-gritty มากมายในการตรวจสอบความถูกต้องของส่วนเล็ก ๆ ของโครงสร้าง มันเหมาะสมที่จะแยกมันออกเป็นวิธีการและเรียกมันจากวิธีสาธารณะหนึ่งวิธี แต่มันก็ไม่สมเหตุสมผลเลยที่จะมีวิธีเล็ก ๆ ทั้งหมดที่ประกอบกันเป็นแบบสาธารณะ
Nether

59

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


ความจริงที่แท้จริง ปัญหาคือเมื่อการใช้งานมีความซับซ้อนมากขึ้นเช่นถ้าวิธีสาธารณะหนึ่งเรียกวิธีการส่วนตัวหลายวิธี อันไหนที่ล้มเหลว หรือถ้าเป็นวิธีส่วนตัวที่ซับซ้อนที่ไม่สามารถสัมผัสหรือถ่ายโอนไปยังคลาสใหม่ได้
Z. Khullah

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

39

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

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

ตัวอย่างเช่นถ้าวิธีการที่จะได้รับการทดสอบอยู่ในแล้วโทรทดสอบของคุณควรจะอยู่ในsrc/main/java/mypackage/MyClass.java src/test/java/mypackage/MyClassTest.javaด้วยวิธีนี้คุณจะสามารถเข้าถึงวิธีทดสอบในชั้นทดสอบของคุณ


1
ฉันไม่รู้เกี่ยวกับอันนี้มันน่าสนใจฉันยังคงคิดว่าถ้าคุณต้องการการบอกกล่าวแบบนั้นคุณมีปัญหาด้านการออกแบบ
Seb

2
สำหรับฉันนี่เป็นเหมือนการพูดแทนที่จะให้กุญแจแบบ one-off ให้กับผู้คุมไฟของคุณเพื่อทดสอบสว่านไฟคุณปล่อยให้ประตูหน้าของคุณถูกปลดล็อกพร้อมกับมีป้ายบอกว่า - "ปลดล็อคสำหรับการทดสอบ - ถ้าคุณไม่ได้มาจาก บริษัท ของเราโปรดอย่าป้อน "
Fr Jeremy Krieg

@FrJeremyKrieg - นั่นหมายความว่าอย่างไร
MasterJoe2

1
@ MasterJoe2: จุดประสงค์ของการทดสอบไฟคือเพื่อปรับปรุงความปลอดภัยของอาคารของคุณจากความเสี่ยงของไฟ แต่คุณต้องให้ผู้คุมเข้าถึงอาคารของคุณ หากคุณปลดล็อคประตูหน้าอย่างถาวรคุณจะเพิ่มความเสี่ยงในการเข้าถึงโดยไม่ได้รับอนุญาตและทำให้ปลอดภัยน้อยลง เช่นเดียวกับรูปแบบ VisibleForTesting - คุณเพิ่มความเสี่ยงของการเข้าถึงโดยไม่ได้รับอนุญาตเพื่อลดความเสี่ยงของ "ไฟ" ดีกว่าที่จะให้สิทธิ์การเข้าถึงแบบครั้งเดียวสำหรับการทดสอบเฉพาะเมื่อคุณต้องการ (เช่นใช้การสะท้อน) แทนที่จะปล่อยให้มันปลดล็อคอย่างถาวร (ไม่ใช่ส่วนตัว)
Fr Jeremy Krieg

34

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

ฉันใช้junitx.util.PrivateAccessor -packageสำหรับ Java liners หนึ่งที่เป็นประโยชน์มากมายสำหรับการเข้าถึงวิธีการส่วนตัวและเขตข้อมูลส่วนตัว

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

2
ตรวจสอบให้แน่ใจว่าได้ดาวน์โหลดแพ็คเกจJUnit-addons ( sourceforge.net/projects/junit-addons ) ทั้งหมดไม่ใช่แพ็คเกจ PrivateAccessor ที่แนะนำสำหรับการปลอมแหล่งที่มา
skia.heliou

2
และให้แน่ใจว่าเมื่อคุณใช้Classเป็นพารามิเตอร์แรกของคุณในวิธีการเหล่านี้คุณจะเข้าถึงเฉพาะstaticสมาชิก
skia.heliou

31

ในSpring Frameworkคุณสามารถทดสอบวิธีส่วนตัวโดยใช้วิธีนี้:

ReflectionTestUtils.invokeMethod()

ตัวอย่างเช่น:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

วิธีการแก้ปัญหาที่สะอาดและกระชับที่สุดในตอนนี้ แต่ถ้าคุณใช้ Spring เท่านั้น
Denis Nikolaenko

28

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

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

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

ข้อดี:

  • สามารถทดสอบความละเอียดปลีกย่อย

ข้อเสีย:

  • รหัสทดสอบจะต้องอยู่ในไฟล์เดียวกับซอร์สโค้ดซึ่งยากต่อการดูแลรักษา
  • เช่นเดียวกันกับไฟล์เอาต์พุต. class จะต้องอยู่ในแพ็คเกจเดียวกันตามที่ได้ประกาศไว้ในซอร์สโค้ด

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

นี่คือตัวอย่างที่ซับซ้อนของวิธีการทำงาน:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

ชั้นในจะถูกคอมไพล์ClassToTest$StaticInnerTestด้วย

ดูเพิ่มเติมที่: เคล็ดลับ Java 106: คลาสภายในคงที่เพื่อความสนุกสนานและผลกำไร


26

อย่างที่คนอื่นพูดว่า ... อย่าทดสอบวิธีส่วนตัวโดยตรง นี่คือความคิดบางอย่าง:

  1. รักษาทุกวิธีให้เล็กและเน้น (ทดสอบง่ายค้นหาสิ่งที่ผิดได้ง่าย)
  2. ใช้เครื่องมือครอบคลุมรหัส ฉันชอบCobertura (โอ้สุขสันต์วันดูเหมือนรุ่นใหม่หมดแล้ว!)

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


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

ฉันได้เห็นคลาสที่มีวิธีสาธารณะเพียงอย่างเดียวเท่านั้น [] และพวกมันก็ปรากฏขึ้นที่ GUI รวมถึงเชื่อมต่อฐานข้อมูลและเว็บเซิร์ฟเวอร์สองแห่งทั่วโลก ง่ายต่อการพูดว่า "อย่าทดสอบทางอ้อม" ...
Audrius Meskauskas

26

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

การทดสอบวิธีการส่วนตัวควรได้รับการทดสอบโดยการดีบักก่อนที่จะทำการทดสอบหน่วยของคุณเกี่ยวกับวิธีการสาธารณะ

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

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


2
แม้ว่าสิ่งนี้จะเป็นความจริง แต่ก็สามารถนำไปสู่การทดสอบที่ซับซ้อนมาก - เป็นรูปแบบที่ดีกว่าในการทดสอบวิธีการครั้งเดียว (การทดสอบหน่วย) แทนที่จะเป็นทั้งกลุ่ม ไม่ใช่คำแนะนำที่แย่มาก แต่ฉันคิดว่ามีคำตอบที่ดีกว่าที่นี่ - นี่ควรเป็นทางเลือกสุดท้าย
Bill K

24

หากใช้ Spring, ReflectionTestUtilsจะมีเครื่องมือที่มีประโยชน์บางอย่างที่ช่วยเหลือได้โดยง่าย ตัวอย่างเช่นการตั้งค่าการเยาะเย้ยในสมาชิกส่วนตัวโดยไม่ถูกบังคับให้เพิ่ม setter สาธารณะที่ไม่พึงประสงค์:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

หากคุณพยายามทดสอบโค้ดที่มีอยู่ซึ่งคุณลังเลหรือไม่สามารถเปลี่ยนแปลงได้การสะท้อนกลับเป็นทางเลือกที่ดี

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

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


19

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

ดังนั้นอย่าทดสอบวิธีการส่วนตัว


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

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

16

คำตอบจากหน้าคำถามที่พบบ่อย JUnit.org :

แต่ถ้าคุณต้อง ...

หากคุณกำลังใช้ JDK 1.3 หรือสูงกว่าคุณสามารถใช้การสะท้อนที่จะล้มล้างกลไกการควบคุมการเข้าถึงด้วยความช่วยเหลือของPrivilegedAccessor สำหรับรายละเอียดเกี่ยวกับวิธีการใช้มันอ่านบทความนี้

หากคุณใช้ JDK 1.6 หรือสูงกว่าและใส่คำอธิบายประกอบการทดสอบด้วย @Test คุณสามารถใช้Dp4jเพื่อฉีดการสะท้อนกลับในวิธีการทดสอบของคุณ สำหรับรายละเอียดเกี่ยวกับวิธีการใช้งานให้ดูที่สคริปต์ทดสอบนี้

ป.ล. ฉันเป็นผู้สนับสนุนหลักให้Dp4jถามฉันว่าคุณต้องการความช่วยเหลือหรือไม่ :)


16

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


4
ในฐานะส่วนหนึ่งของไลบรารี jmockit คุณสามารถเข้าถึงคลาส Deencapsulation ซึ่งทำให้การทดสอบวิธีส่วนตัวเป็นเรื่องง่าย: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

ฉันใช้วิธีนี้ตลอดเวลา มีประโยชน์มาก
MedicineMan

14

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


14

หากคุณกำลังใช้ JUnit, มีลักษณะที่junit-addons มันมีความสามารถในการละเว้นรูปแบบการรักษาความปลอดภัย Java และเข้าถึงวิธีการและคุณลักษณะส่วนตัว


13

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

คุณพูดถึงปัญหาประเภทต่างๆ เริ่มจากฟิลด์ส่วนตัวกันก่อน ในกรณีของฟิลด์ส่วนตัวฉันจะเพิ่มตัวสร้างใหม่และฟิลด์ที่แทรกเข้าไปในนั้น แทนสิ่งนี้:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

ฉันได้ใช้สิ่งนี้:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

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

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

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

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

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

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


1
ไม่เห็นด้วยกับการClassToTest(Callable)ฉีด ทำให้ClassToTestมีความซับซ้อนมากขึ้น ง่าย ๆ เข้าไว้. นอกจากนี้ต้องมีคนอื่นบอกClassToTestสิ่งที่ClassToTestควรจะเป็นเจ้านายของ มีสถานที่สำหรับการฉีดตรรกะ แต่นี่ไม่ใช่มัน คุณทำให้ชั้นเรียนยากขึ้นเพื่อรักษาไม่ใช่เรื่องง่าย
Loduwijk

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

2
คุณช่วยอธิบายได้ไหมว่าทำไมมันจะทำให้การเรียนClassToTestยากขึ้น ที่จริงแล้วมันช่วยให้แอปพลิเคชันของคุณง่ายต่อการบำรุงรักษาคุณแนะนำให้สร้างคลาสใหม่สำหรับค่าที่แตกต่างกันทุกค่าที่คุณต้องการในfirstและตัวแปร 'วินาที'
GROX13

1
วิธีการทดสอบไม่ได้เพิ่มความซับซ้อน มันเป็นแค่ชั้นเรียนของคุณที่เขียนไม่ได้แย่มากจนไม่สามารถทดสอบได้
GROX13

บำรุงรักษายากกว่าเนื่องจากคลาสนั้นซับซ้อนกว่า จะง่ายกว่าclass A{ A(){} f(){} } class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }ถ้านั่นไม่เป็นความจริงและถ้ามันเป็นความจริงว่าการจัดหาตรรกะของคลาสเป็นอาร์กิวเมนต์ตัวสร้างได้ง่ายกว่าที่จะรักษาแล้วเราจะผ่านตรรกะทั้งหมดเป็นอาร์กิวเมนต์ตัวสร้าง ฉันไม่เห็นว่าคุณจะอ้างสิทธิ์ได้อย่างไรclass B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }ทำให้การดูแลรักษาง่ายขึ้น มีหลายกรณีที่การฉีดเช่นนี้เหมาะสม แต่ฉันไม่เห็นตัวอย่างของคุณว่า "ง่ายต่อการบำรุงรักษา"
Loduwijk

12

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

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

หากสิ่งทั้งหมดข้างต้นไม่ตรงกับความต้องการของคุณให้ใช้วิธีการไตร่ตรองเพื่อเข้าถึงวิธีการส่วนตัว


คุณกำลังผสม "ป้องกัน" กับ "เป็นมิตร" เมธอดที่มีการป้องกันสามารถเข้าถึงได้โดยคลาสที่มีอ็อบเจ็กต์ที่สามารถกำหนดให้กับคลาสเป้าหมาย (เช่นคลาสย่อย)
Sayo Oladeji

1
จริงๆแล้วไม่มี "เป็นมิตร" ใน Java คำว่า "แพคเกจ" และมันถูกระบุโดยการขาดตัวแก้ไขส่วนตัว / สาธารณะ / ป้องกัน ฉันเพิ่งจะแก้ไขคำตอบนี้ แต่ก็มีคำตอบที่ดีจริงๆที่พูดไปแล้ว - ดังนั้นฉันขอแนะนำให้ลบมันทิ้ง
Bill K

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

11

ตามที่แนะนำไว้ข้างต้นวิธีที่ดีคือการทดสอบผ่านอินเทอร์เฟซสาธารณะของคุณ

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


คุณไม่ควรทดสอบทางอ้อม! ไม่เพียง แต่สัมผัสผ่านการครอบคลุม ทดสอบว่าส่งมอบผลลัพธ์ที่คาดหวัง!
AlexWien

11

นี่คือฟังก์ชั่นทั่วไปของฉันเพื่อทดสอบฟิลด์ส่วนตัว:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

10

โปรดดูตัวอย่างด้านล่าง;

ควรเพิ่มคำสั่งการนำเข้าต่อไปนี้:

import org.powermock.reflect.Whitebox;

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

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

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

ถ้าคุณมีโค้ดบางส่วนด้วยวิธีการส่วนตัวหรือสาขาหรือการก่อสร้างงานคุณสามารถใช้BoundBox มันทำสิ่งที่คุณกำลังมองหา ด้านล่างนี้เป็นตัวอย่างของการทดสอบที่เข้าถึงสองฟิลด์ส่วนตัวของกิจกรรม Android เพื่อทดสอบ:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

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

มันเหมาะสำหรับการทดสอบรหัสดั้งเดิม ใช้อย่างระมัดระวัง ;)

https://github.com/stephanenicolas/boundbox


1
เพิ่งลองมัน BoundBox เป็นวิธีที่ง่ายและสง่างาม!
forhas

9

ก่อนอื่นฉันจะทิ้งคำถามนี้: ทำไมสมาชิกส่วนตัวของคุณต้องทำการทดสอบแยก พวกมันซับซ้อนหรือไม่ที่ให้พฤติกรรมที่ซับซ้อนเช่นนี้เพื่อต้องการการทดสอบนอกเหนือจากพื้นผิวสาธารณะ? เป็นการทดสอบหน่วยไม่ใช่การทดสอบ 'บรรทัดของรหัส' อย่าเหงื่อสิ่งเล็ก ๆ น้อย ๆ

หากพวกเขาใหญ่ขนาดใหญ่พอที่สมาชิกส่วนตัวเหล่านี้แต่ละคนมีขนาดใหญ่ 'ซับซ้อน' - พิจารณาการปรับโครงสร้างสมาชิกเอกชนดังกล่าวออกจากชั้นนี้

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


3
เพราะบ่อยครั้งที่โค้ดบางส่วนจากวิธีสาธารณะถูกปรับเปลี่ยนเป็นวิธีส่วนตัวภายในและเป็นชิ้นส่วนที่สำคัญของตรรกะที่คุณอาจผิด คุณต้องการทดสอบสิ่งนี้อย่างอิสระจากวิธีสาธารณะ
oxbow_lakes

แม้แต่รหัสที่สั้นที่สุดในบางครั้งที่ไม่มีการทดสอบหน่วยก็ไม่ถูกต้อง เพียงแค่ลองคำนวณความแตกต่างระหว่างมุม geograhpical 2 มุม รหัส 4 บรรทัดและส่วนใหญ่จะไม่ถูกต้องในการลองครั้งแรก วิธีการดังกล่าวต้องการการทดสอบหน่วยเพราะรูปแบบฐานของรหัส trustfull (Ans รหัสที่มีประโยชน์ดังกล่าวสามารถเป็นสาธารณะเช่นกันมีประโยชน์น้อยป้องกัน
AlexWien

8

ฉันเพิ่งมีปัญหานี้และเขียนเครื่องมือเล็ก ๆ น้อย ๆ ที่เรียกว่าPicklockที่หลีกเลี่ยงปัญหาการใช้ Java reflection API อย่างชัดเจนสองตัวอย่าง:

วิธีการโทรเช่นprivate void method(String s)- โดยการสะท้อน Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

วิธีการโทรเช่นprivate void method(String s)- โดย Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

การตั้งค่าฟิลด์เช่นprivate BigInteger amount;- โดยการสะท้อน Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

การตั้งค่าฟิลด์เช่นprivate BigInteger amount;- โดย Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

7

PowerMockito สร้างขึ้นเพื่อสิ่งนี้ ใช้การพึ่งพา Maven

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

จากนั้นคุณสามารถทำได้

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.