วิธีการแก้ไขข้อผิดพลาดในการทดสอบหลังจากการเขียนการใช้งาน


21

การกระทำที่ดีที่สุดใน TDD คืออะไรหากหลังจากใช้ตรรกะอย่างถูกต้องการทดสอบยังคงล้มเหลว (เพราะมีข้อผิดพลาดในการทดสอบ)?

ตัวอย่างเช่นสมมติว่าคุณต้องการพัฒนาฟังก์ชั่นต่อไปนี้:

int add(int a, int b) {
    return a + b;
}

สมมติว่าเราพัฒนามันในขั้นตอนต่อไปนี้:

  1. ทดสอบการเขียน (ยังไม่มีฟังก์ชั่น):

    // test1
    Assert.assertEquals(5, add(2, 3));
    

    ผลลัพธ์มีข้อผิดพลาดในการรวบรวม

  2. เขียนการใช้งานฟังก์ชันหลอกตา:

    int add(int a, int b) {
        return 5;
    }
    

    ผลลัพธ์: test1ผ่าน

  3. เพิ่มกรณีทดสอบอื่น:

    // test2 -- notice the wrong expected value (should be 11)!
    Assert.assertEquals(12, add(5, 6));
    

    ผลลัพธ์: test2ล้มเหลวtest1ยังคงผ่านไป

  4. เขียนการใช้งานจริง:

    int add(int a, int b) {
        return a + b;
    }
    

    ผลลัพธ์: test1ยังคงผ่านและtest2ยังคงล้มเหลว (ตั้งแต่11 != 12)

ในกรณีนี้จะเป็นการดีกว่าหรือไม่:

  1. ถูกต้องtest2และดูว่าตอนนี้ผ่านไปหรือ
  2. ลบส่วนใหม่ของการใช้งาน (เช่นกลับไปที่ขั้นตอนที่ 2 ด้านบน) แก้ไขtest2และปล่อยให้มันล้มเหลวจากนั้นนำการใช้งานที่ถูกต้องกลับมาใช้ใหม่ (ขั้นตอนที่ 4 ด้านบน)

หรือมีบางวิธีที่ฉลาดกว่า?

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

แก้ไข (เพื่อตอบสนองต่อคำตอบของ @Thomas Junk):

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


3
การปรับโครงสร้างกับแถบสีแดงเป็นแนวคิดที่เกี่ยวข้อง
RubberDuck

5
เห็นได้ชัดว่าคุณต้องทำ TDD ใน TDD ของคุณ
Blrfl

17
หากใครเคยถามฉันว่าทำไมฉันถึงสงสัย TDD ฉันจะชี้ไปที่คำถามนี้ นี่คือ Kafkaesque
Traubenfuchs

@Blrfl นั่นคือสิ่งที่ Xibit บอกกับเรา»ฉันใส่ TDD ใน TDD เพื่อที่คุณจะได้ TDD ในขณะที่ TDDing «: D
Thomas Junk

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

คำตอบ:


31

สิ่งที่สำคัญอย่างยิ่งคือการที่คุณเห็นการทดสอบทั้งผ่านและล้มเหลว

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

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


Refactoring Against The Red Barให้ขั้นตอนอย่างเป็นทางการสำหรับการทดสอบการทำงานอีกครั้ง:

  • ทำการทดสอบ
    • สังเกตแถบสีเขียว
    • ทำลายรหัสที่กำลังทดสอบ
  • ทำการทดสอบ
    • สังเกตแถบสีแดง
    • ทำการทดสอบซ้ำอีกครั้ง
  • ทำการทดสอบ
    • สังเกตแถบสีแดง
    • ยกเลิกการทำลายรหัสการทดสอบ
  • ทำการทดสอบ
    • สังเกตแถบสีเขียว

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

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

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

แนะนำการทดสอบสีเขียว

  • ทำการทดสอบ
    • สังเกตแถบสีเขียว
    • ทำลายรหัสที่กำลังทดสอบ
  • ทำการทดสอบ
    • สังเกตแถบสีแดง
    • ยกเลิกการทำลายรหัสการทดสอบ
  • ทำการทดสอบ
    • สังเกตแถบสีเขียว

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

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

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

ฉันขอขอบคุณRubberDuckสำหรับการเชื่อมโยงกอดแถบสีแดง


2
ฉันชอบคำตอบนี้ดีที่สุด: สิ่งสำคัญคือการดูการทดสอบล้มเหลวด้วยรหัสที่ไม่ถูกต้องดังนั้นฉันจะลบ / แสดงความคิดเห็นรหัสแก้ไขการทดสอบและดูพวกเขาล้มเหลวใส่รหัสกลับ (อาจแนะนำข้อผิดพลาดโดยเจตนาเพื่อทำการทดสอบ การทดสอบ) และแก้ไขรหัสเพื่อให้ทำงานได้ XP เป็นอย่างมากที่จะลบและเขียนใหม่อย่างสมบูรณ์ แต่บางครั้งคุณก็ต้องปฏิบัติ ;)
GolezTrol

@GolezTrol ฉันคิดว่าคำตอบของฉันพูดในสิ่งเดียวกันดังนั้นฉันขอขอบคุณข้อเสนอแนะใด ๆ ที่คุณมีเกี่ยวกับว่าไม่ชัดเจน
jonrsharpe

@ jonrsharpe คำตอบของคุณก็ดีเช่นกันและฉันก็สนับสนุนมันก่อนที่จะอ่านข้อความนี้ แต่ที่คุณเข้มงวดมากในการคืนค่ารหัส CandiedOrange แนะนำวิธีปฏิบัติเพิ่มเติมที่สนใจฉันมากขึ้น
GolezTrol

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

10

เป้าหมายโดยรวมคืออะไรคุณต้องการบรรลุ

  • ทำแบบทดสอบที่ดี?

  • ทำให้การใช้งานถูกต้องหรือไม่

  • ทำ TTD อย่างถูกต้องทางศาสนา ?

  • ไม่มีข้อใดถูก?

บางทีคุณอาจคิดถึงความสัมพันธ์ของคุณกับการทดสอบและการทดสอบ

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

ยกตัวอย่างของคุณ:

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

int add(int a, int b) {
    return a + b;
}

เมื่อแรกเห็นเราทั้งสองจะเห็นว่านี่คือการดำเนินการของการเพิ่ม

แต่สิ่งที่เรากำลังทำจริงๆคือไม่ได้บอกว่ารหัสนี้คือการดำเนินการของadditionมันเพียง แต่จะทำงานในระดับหนึ่งเช่นเดียว: คิดจำนวนเต็มล้น

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

มุมมองทางปรัชญาที่ค่อนข้างนี้มีผลหลายอย่าง

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

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

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


TL; DR

การกระทำที่ดีที่สุดใน TDD คืออะไรหากหลังจากใช้ตรรกะอย่างถูกต้องการทดสอบยังคงล้มเหลว (เพราะมีข้อผิดพลาดในการทดสอบ)?

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


1
ฉันคิดว่าคำถามเกี่ยวกับเป้าหมายโดยรวมนั้นสำคัญมากขอบคุณที่นำมันมาใช้ สำหรับฉัน Prio ที่สูงที่สุดมีดังต่อไปนี้: 1. การใช้งานที่ถูกต้อง 2. การทดสอบ "ดี" (หรือฉันอยากจะบอกว่าการทดสอบ "มีประโยชน์" / "การออกแบบที่ดี") ฉันเห็นว่า TDD เป็นเครื่องมือที่เป็นไปได้สำหรับการบรรลุเป้าหมายทั้งสองนั้น ดังนั้นในขณะที่ฉันไม่ต้องการติดตาม TDD อย่างเคร่งศาสนาในบริบทของคำถามนี้ฉันส่วนใหญ่สนใจในมุมมอง TDD ฉันจะแก้ไขคำถามเพื่อชี้แจงนี้
Attilio

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

1
@JerryJeremiah จุดของฉันคือ: สิ่งที่การทดสอบของคุณควรครอบคลุมขึ้นอยู่กับกรณีการใช้งานของคุณ สำหรับกรณีการใช้งานที่คุณเพิ่มขึ้นพวงของตัวเลขเดียวอัลกอริทึมที่ดีพอ หากคุณรู้ว่ามีโอกาสมากที่คุณจะรวม "ตัวเลขจำนวนมาก" datatypeนั่นเป็นทางเลือกที่ผิดอย่างชัดเจน การทดสอบจะเปิดเผยว่า: ความคาดหวังของคุณจะเป็น»ใช้งานได้กับคนจำนวนมาก«และในหลายกรณีไม่ได้พบกัน จากนั้นคำถามจะเป็นวิธีการจัดการกับกรณีเหล่านั้น พวกเขาเป็นมุมกรณีหรือไม่ เมื่อไหร่จะจัดการกับพวกเขาได้อย่างไร บางทีประโยคที่มีข้อผิดพลาดบางอย่างอาจช่วยป้องกันความยุ่งเหยิงยิ่งขึ้น คำตอบคือบริบทที่ถูกผูกไว้
Thomas Junk

7

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


เราจะตรวจสอบได้อย่างไรว่าการทดสอบล้มเหลวเนื่องจากสาเหตุที่เราคาดไว้?
Basilevs

2
@Basilevs คุณ: 1. ตั้งสมมุติฐานว่าอะไรคือสาเหตุของความล้มเหลว 2. รันการทดสอบ และ 3. อ่านข้อความความล้มเหลวที่เกิดขึ้นและเปรียบเทียบ บางครั้งสิ่งนี้ยังแนะนำวิธีที่คุณสามารถเขียนการทดสอบใหม่เพื่อให้ข้อผิดพลาดที่มีความหมายมากขึ้น (ตัวอย่างเช่นassertTrue(5 == add(2, 3))ให้ผลลัพธ์ที่มีประโยชน์น้อยกว่าassertEqual(5, add(2, 3))แม้ว่าพวกเขาทั้งสองกำลังทดสอบสิ่งเดียวกัน)
jonrsharpe

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

1
@Basilevs อะไรนะ? สมมติฐานของคุณที่นี่ที่ขั้นตอนที่ 3 จะเป็น"การทดสอบล้มเหลวเนื่องจาก 5 ไม่เท่ากับ 12" การรันการทดสอบจะแสดงให้คุณเห็นว่าการทดสอบล้มเหลวด้วยเหตุผลนั้นในกรณีที่คุณดำเนินการต่อหรือด้วยเหตุผลอื่นซึ่งในกรณีนี้คุณคิดว่าทำไม บางทีนี่อาจเป็นปัญหาทางภาษา แต่ก็ไม่ชัดเจนสำหรับฉันในสิ่งที่คุณกำลังแนะนำ
jonrsharpe

5

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

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

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


0

ฉันว่านี่เป็นกรณีสำหรับระบบควบคุมเวอร์ชันที่คุณโปรดปราน:

  1. ขั้นตอนการแก้ไขของการทดสอบทำให้การเปลี่ยนแปลงรหัสของคุณในไดเรกทอรีการทำงานของคุณ
    กระทำด้วยข้อความที่Fixed test ... to expect correct outputเกี่ยวข้อง

    ด้วยgitสิ่งนี้อาจต้องใช้git add -pหากการทดสอบและการใช้งานอยู่ในไฟล์เดียวกันมิฉะนั้นคุณสามารถแยกไฟล์ทั้งสองออกจากกันได้

  2. ยอมรับรหัสการนำไปใช้งาน

  3. ย้อนเวลากลับไปเพื่อทดสอบการกระทำที่เกิดขึ้นในขั้นตอนที่ 1 การทำให้แน่ใจว่าการทดสอบจริงล้มเหลว

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

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