การสอดแนมในการปฏิบัติที่ไม่ดีในชั้นเรียนทดสอบหรือไม่?


14

ฉันกำลังทำงานในโครงการที่การโทรภายในคลาสเป็นปกติ แต่ผลลัพธ์จะมีค่าง่าย ๆ หลายครั้ง ตัวอย่าง ( ไม่ใช่รหัสจริง ):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

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

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

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

ข้อกังวลที่นี่คือการใช้งาน SUT นั้นมีการเปลี่ยนแปลงอย่างมีประสิทธิภาพและอาจเป็นปัญหาในการทดสอบให้สอดคล้องกับการใช้งาน มันเป็นเรื่องจริงเหรอ? มีวิธีปฏิบัติที่ดีที่สุดในการหลีกเลี่ยงความหายนะของการเรียกใช้เมธอดภายในหรือไม่?

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

คำตอบ:


15

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

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


1
ฉันเข้าใจแนวคิดของการทดสอบหน่วยมานานแล้วและได้เขียนสิ่งเหล่านี้สำเร็จ มันแค่หลอกว่ามีอะไรบางอย่างที่ดูเรียบง่ายบนกระดาษมันดูแย่ลงในรหัสและในที่สุดฉันก็ต้องเผชิญกับบางสิ่งที่มีอินเทอร์เฟซที่เรียบง่ายมาก ๆ
allprog

@ allprog เมื่อคุณต้องเยาะเย้ยมากดูเหมือนว่าคุณมีการพึ่งพาระหว่างคลาสมากเกินไป คุณพยายามลดการเชื่อมต่อระหว่างพวกเขาหรือไม่?
Philipp

@ allprog หากคุณอยู่ในสถานการณ์นั้นการออกแบบคลาสคือการตำหนิ
itsbruce

มันเป็นตัวแบบข้อมูลที่ทำให้ปวดหัว จะต้องปฏิบัติตามกฎ ORM และข้อกำหนดอื่น ๆ อีกมากมาย ด้วยตรรกะทางธุรกิจที่บริสุทธิ์และรหัสไร้สัญชาติมันง่ายกว่ามากในการทดสอบหน่วย
allprog

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

4

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


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

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

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

4

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

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


ฟังดูดี. แต่ในความเป็นจริง "สตริง" ฉันต้องพิมพ์และเรียกมันว่ารหัสเป็นภาษาที่รู้น้อยมากเกี่ยวกับฟังก์ชั่น ในทางทฤษฎีฉันสามารถอธิบายปัญหาได้อย่างง่ายดายและทำการทดแทนที่นี่และที่นั่นเพื่อทำให้ง่ายขึ้น ในรหัสฉันต้องเพิ่มเสียงวากยสัมพันธ์มากมายเพื่อให้ได้ความยืดหยุ่นนี้ซึ่งทำให้ฉันไม่สามารถใช้งานได้ หากวิธีการaที่มีการเรียกร้องให้วิธีการbในระดับเดียวกันแล้วการทดสอบจะต้องมีการทดสอบa bและไม่มีทางที่จะเปลี่ยนแปลงสิ่งนี้ตราบใดที่bไม่ได้ถูกส่งไปaเป็นพารามิเตอร์ แต่ไม่มีวิธีอื่นที่ฉันเห็น
allprog

1
หากbเป็นส่วนหนึ่งของส่วนต่อประสานสาธารณะก็ควรทำการทดสอบต่อไป หากไม่เป็นเช่นนั้นไม่จำเป็นต้องทำการทดสอบ หากคุณเปิดเผยต่อสาธารณะเพียงเพราะคุณต้องการทดสอบคุณทำผิด
itsbruce

ดูความคิดเห็นของฉันต่อคำตอบของ @ Philip ฉันยังไม่ได้กล่าวถึง แต่ตัวแบบข้อมูลนั้นเป็นที่มาของความชั่วร้าย รหัสไร้สัญชาติบริสุทธิ์เป็นชิ้นส่วนของเค้ก
allprog

2

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

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

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


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

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

"ฉันต้องการทดสอบฟังก์ชั่นที่ซับซ้อน .. โดยไม่ต้องให้พารามิเตอร์ที่มีเหตุผลสำหรับฟังก์ชั่นย่อย" - ฉันไม่เข้าใจสิ่งที่คุณหมายถึงที่นั่น ฟังก์ชันย่อยใด คุณกำลังพูดถึงฟังก์ชั่นภายในที่ใช้โดย 'ฟังก์ชั่นที่ซับซ้อน' หรือไม่?
BT

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

@ allprog "ตรรกะเชิงตรรกะ" - หากซับซ้อนคุณต้องมีการทดสอบที่ซับซ้อน ไม่มีทางรอบนั้น สายลับกำลังจะทำให้มันยากขึ้นและซับซ้อนมากขึ้นสำหรับคุณ คุณควรสร้างฟังก์ชั่นย่อยที่เข้าใจได้ซึ่งคุณสามารถทดสอบได้ด้วยตัวเองแทนที่จะใช้สายลับเพื่อทดสอบพฤติกรรมพิเศษของพวกเขาภายในฟังก์ชั่นอื่น
BT
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.