การดิ้นรนกับการขึ้นต่อเนื่องของวงจรในการทดสอบหน่วย


24

ฉันพยายามฝึกฝน TDD โดยใช้มันเพื่อพัฒนาวิเช่น Bit Vector ฉันบังเอิญใช้สวิฟท์ แต่นี่เป็นคำถามที่ไม่เชื่อเรื่องภาษา

My BitVectoris คือตัวstructที่เก็บ single UInt64และนำเสนอ API เหนือมันที่ให้คุณปฏิบัติต่อมันเหมือนกับคอลเลกชัน รายละเอียดไม่สำคัญมาก แต่มันค่อนข้างง่าย 57 บิตสูงเป็นบิตหน่วยเก็บข้อมูลและ 6 บิตด้านล่างเป็นบิต "นับ" ซึ่งจะบอกคุณว่าบิตหน่วยเก็บข้อมูลจำนวนหนึ่งเก็บค่าที่มีอยู่จริงได้อย่างไร

จนถึงตอนนี้ฉันมีความสามารถที่เรียบง่ายไม่กี่อย่าง:

  1. เครื่องมือเริ่มต้นที่สร้างเวกเตอร์บิตที่ว่างเปล่า
  2. countทรัพย์สินของประเภทInt
  3. isEmptyทรัพย์สินของประเภทBool
  4. ตัวดำเนินการความเสมอภาค ( ==) หมายเหตุ: นี่เป็นตัวดำเนินการค่าความเท่าเทียมกันObject.equals()ใน Java ไม่ใช่ตัวดำเนินการความเท่าเทียมกันอ้างอิงเช่น==ใน Java

ฉันวิ่งเข้าไปในกลุ่มพึ่งพาพึ่งพาวงจร:

  1. BitVectorหน่วยทดสอบที่ทดสอบความจำเป็นในการเริ่มต้นของฉันที่จะตรวจสอบว่ามีการสร้างขึ้นใหม่ สามารถทำได้หนึ่งใน 3 วิธี:

    1. ตรวจสอบ bv.count == 0
    2. ตรวจสอบ bv.isEmpty == true
    3. ตรวจสอบว่า bv == knownEmptyBitVector

    วิธีที่ 1 อาศัยcount, วิธีที่ 2 อาศัยisEmpty(ที่ตัวเองอาศัยcountจึงมีจุดใดที่ใช้มัน) วิธีที่ 3 ==อาศัย ไม่ว่าในกรณีใดฉันไม่สามารถทดสอบตัวเริ่มต้นแยกได้

  2. การทดสอบcountความต้องการในการใช้งานบางอย่างซึ่งทดสอบเบื้องต้นของฉันอย่างหลีกเลี่ยงไม่ได้

  3. การดำเนินการisEmptyอาศัยcount

  4. การดำเนินการต้องอาศัย==count

ฉันสามารถแก้ปัญหานี้ได้บางส่วนโดยการแนะนำ API ส่วนตัวที่สร้างBitVectorจากรูปแบบบิตที่มีอยู่ (ในฐานะUInt64) สิ่งนี้ทำให้ฉันสามารถเริ่มต้นค่าโดยไม่ต้องทดสอบ initializers อื่น ๆ เพื่อที่ฉันจะได้ "สายรัดบูต" ทางของฉัน

เพื่อให้การทดสอบหน่วยของฉันเป็นการทดสอบหน่วยอย่างแท้จริงฉันพบว่าตัวเองกำลังทำแฮ็กจำนวนมากซึ่งทำให้รหัสการทดสอบและการทดสอบของฉันซับซ้อนขึ้นอย่างมาก

คุณจะแก้ไขปัญหาเหล่านี้ได้อย่างไร?


20
คุณใช้มุมมองที่แคบเกินไปกับคำว่า "หน่วย" BitVectorเป็นขนาดหน่วยที่สมบูรณ์แบบสำหรับการทดสอบหน่วยและแก้ไขปัญหาของคุณที่สมาชิกสาธารณะBitVectorต้องการซึ่งกันและกันเพื่อทำการทดสอบที่มีความหมาย
Bart van Ingen Schenau

คุณรู้รายละเอียดการใช้งานมากเกินไป คือการพัฒนาของคุณจริงๆ test- ขับเคลื่อน ?
herby

@herby ไม่นั่นเป็นเหตุผลที่ฉันฝึกซ้อม แม้ว่าจะดูเหมือนว่าเป็นมาตรฐานที่ไม่สามารถบรรลุได้จริงๆ ฉันไม่ได้ทำอะไรเลยฉันไม่ได้ตั้งโปรแกรมอะไรเลยโดยไม่ได้คำนึงถึงสิ่งที่จะนำไปปฏิบัติ
Alexander - Reinstate Monica

@Alexander คุณควรพยายามที่จะผ่อนคลายมิฉะนั้นจะทำการทดสอบก่อน แต่ไม่ใช่การทดสอบ เพียงแค่พูดคลุมเครือ "ฉันจะทำเวกเตอร์เล็กน้อยด้วย int 64 บิตหนึ่งเป็นร้านสำรองข้อมูล" และนั่นคือ; จากจุดนั้นทำ TDD red-green-refactor ทีละอัน รายละเอียดการนำไปใช้รวมถึง API ควรปรากฏจากการพยายามทำการทดสอบ (ในอดีต) และจากการเขียนการทดสอบเหล่านั้นตั้งแต่แรก (หลัง)
herby

คำตอบ:


66

คุณกังวลเกี่ยวกับรายละเอียดการใช้งานมากเกินไป

มันไม่สำคัญว่าในการดำเนินงานในปัจจุบันของคุณ , isEmptyอาศัยcount(หรืออะไรก็ตามความสัมพันธ์อื่น ๆ ที่คุณอาจมี): สิ่งที่คุณควรจะได้รับการดูแลเกี่ยวกับการเป็นอินเตอร์เฟซที่สาธารณะ ตัวอย่างเช่นคุณสามารถทดสอบสามแบบ:

  • count == 0ว่าวัตถุเริ่มต้นใหม่มี
  • นั่นเป็นวัตถุที่เริ่มต้นใหม่ได้ isEmpty == true
  • นั่นเป็นวัตถุเริ่มต้นใหม่เท่ากับวัตถุเปล่าที่รู้จัก

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

สิ่งที่คล้ายกันนำไปใช้กับประเด็นอื่น ๆ ของคุณ - อย่าลืมทดสอบอินเทอร์เฟซสาธารณะไม่ใช่การใช้งานภายในของคุณ คุณอาจพบว่า TDD มีประโยชน์ที่นี่เนื่องจากคุณจะต้องทำการทดสอบที่จำเป็นisEmptyก่อนที่จะเขียนการใช้งานใด ๆ เลย


6
@Alexander คุณฟังดูเหมือนคนที่ต้องการคำจำกัดความที่ชัดเจนของการทดสอบหน่วย สิ่งที่ดีที่สุดที่ฉันรู้มาจากMichael Feathers
candied_orange

14
@Alexander คุณปฏิบัติต่อแต่ละวิธีว่าเป็นส่วนที่ทดสอบได้อย่างอิสระของโค้ด นั่นคือที่มาของความยากลำบากของคุณ ความยากลำบากเหล่านี้จะหายไปถ้าคุณทดสอบวัตถุโดยรวมโดยไม่ต้องพยายามแบ่งมันออกเป็นส่วนเล็ก ๆ การพึ่งพาระหว่างวัตถุนั้นไม่สามารถเปรียบเทียบได้กับการพึ่งพาระหว่างวิธีการ
amon

9
@Alexander "ส่วนหนึ่งของรหัส" เป็นการวัดโดยพลการ เพียงแค่เริ่มต้นตัวแปรคุณกำลังใช้ "ส่วนของรหัส" มากมาย สิ่งสำคัญคือคุณกำลังทดสอบหน่วยพฤติกรรมที่เหนียวแน่นตามที่คุณกำหนด
Ant P

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

4
@Alexander ลองดูรูปแบบ "Arrange, Act, Assert" เพื่อทำการทดสอบ โดยทั่วไปคุณตั้งค่าวัตถุในสถานะใด ๆ ที่จำเป็นต้องมีใน (จัดเรียง) เรียกวิธีที่คุณกำลังทดสอบจริง (Act) แล้วตรวจสอบว่าสถานะนั้นเปลี่ยนแปลงไปตามความคาดหวังของคุณ (ยืนยัน). สิ่งที่คุณตั้งค่าใน Arrange จะเป็น "เงื่อนไข" สำหรับการทดสอบ
GalacticCowboy

5

คุณจะแก้ไขปัญหาเหล่านี้ได้อย่างไร?

คุณทบทวนความคิดของคุณว่า "การทดสอบหน่วย" คืออะไร

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

ในทางปฏิบัติมักมีลักษณะเช่นนี้

// GIVEN
obj = new Object(...)

// THEN
assert object.read(...)

หรือ

// GIVEN
obj = new Object(...)

// WHEN
object.change(...)

// THEN
assert object.read(...)

คำศัพท์ "การทดสอบหน่วย" - มันมีประวัติอันยาวนานที่ไม่ดีมาก

ฉันเรียกพวกเขาว่าการทดสอบหน่วย แต่พวกเขาไม่ตรงกับคำจำกัดความที่ยอมรับได้ของการทดสอบหน่วยดีมาก - Kent Beck การพัฒนาแบบทดสอบที่ขับเคลื่อนด้วยตัวอย่าง

Kent เขียนรุ่นแรกของ SUnit ในปี 1994พอร์ตไปยัง JUnit คือในปี 1998 ร่างแรกของหนังสือ TDD คือต้นปี 2002 ความสับสนมีเวลาแพร่กระจายมาก

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

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

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


1

โดยทั่วไป (แม้ว่าจะไม่ได้ใช้ TDD) คุณควรพยายามเขียนการทดสอบให้มากที่สุดเท่าที่จะเป็นไปได้ในขณะที่แสร้งทำเป็นว่าคุณไม่รู้ว่าจะใช้งานอย่างไร

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

ลักษณะของกราฟการโทรภายใต้การทดสอบนั้นไม่เกี่ยวข้องตราบใดที่การทดสอบนั้นสมเหตุสมผลและได้รับการบำรุงรักษาอย่างดี

ฉันคิดว่าปัญหาของคุณคือความเข้าใจใน TDD

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

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

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