คุณควรจะเล่นเกม Yahtzee อย่างไร?


36

สมมติว่าคุณกำลังเขียนสไตล์เกม TDT ของ Yahtzee คุณต้องการทดสอบส่วนของรหัสที่กำหนดว่าชุดแม่พิมพ์ห้าม้วนเป็นบ้านเต็มหรือไม่ เท่าที่ฉันรู้เมื่อทำ TDD คุณทำตามหลักการเหล่านี้:

  • เขียนแบบทดสอบก่อน
  • เขียนสิ่งที่ง่ายที่สุดที่เป็นไปได้
  • ปรับแต่งและ refactor

ดังนั้นการทดสอบครั้งแรกอาจมีลักษณะเช่นนี้:

public void Returns_true_when_roll_is_full_house()
{
    FullHouseTester sut = new FullHouseTester();
    var actual = sut.IsFullHouse(1, 1, 1, 2, 2);

    Assert.IsTrue(actual);
}

เมื่อทำตาม "เขียนสิ่งที่ง่ายที่สุดที่เป็นไปได้" คุณควรเขียนIsFullHouseวิธีดังนี้:

public bool IsFullHouse(int roll1, int roll2, int roll3, int roll4, int roll5)
{
    if (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
    {
        return true;
    }

    return false;
}

ผลลัพธ์นี้ในการทดสอบสีเขียว แต่การใช้งานไม่สมบูรณ์

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

คุณจะทดสอบสิ่งนี้อย่างไร

ปรับปรุง

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

ประสบการณ์เชิงปฏิบัติของฉันกับการทดสอบหน่วย (โดยเฉพาะการใช้วิธีการ TDD) มี จำกัด มาก ฉันจำได้ว่ากำลังดูบันทึกของ TDD Masterclass ของ Roy Osherove ใน Tekpub หนึ่งในตอนที่เขาสร้างสไตล์เครื่องคิดเลข String ข้อมูลจำเพาะทั้งหมดของเครื่องคำนวณสตริงสามารถดูได้ที่นี่: http://osherove.com/tdd-kata-1/

เขาเริ่มต้นด้วยการทดสอบเช่นนี้:

public void Add_with_empty_string_should_return_zero()
{
    StringCalculator sut = new StringCalculator();
    int result = sut.Add("");

    Assert.AreEqual(0, result);
}

นี่เป็นผลลัพธ์ในการใช้Addวิธีการแรก:

public int Add(string input)
{
    return 0;
}

จากนั้นเพิ่มการทดสอบนี้:

public void Add_with_one_number_string_should_return_number()
{
    StringCalculator sut = new StringCalculator();
    int result = sut.Add("1");

    Assert.AreEqual(1, result);
}

และAddวิธีการนั้นจะ refactored:

public int Add(string input)
{
    if (input.Length == 0)
    {
        return 0;
    }

    return 1;
}

หลังจากแต่ละขั้นตอนรอยพูดว่า "เขียนสิ่งที่ง่ายที่สุดที่จะใช้ได้"

ดังนั้นฉันคิดว่าฉันจะลองวิธีนี้เมื่อพยายามทำเกม Yahtzee สไตล์ TDD


8
"เขียนสิ่งที่ง่ายที่สุดที่เป็นไปได้ที่ใช้งานได้" เป็นตัวย่อ คำแนะนำที่ถูกต้องคือ "เขียนสิ่งที่ง่ายที่สุดเท่าที่จะทำได้ซึ่งไม่ใช่ความคิดที่สมบูรณ์และเห็นได้ชัดว่าไม่ถูกต้อง " ดังนั้นไม่คุณไม่ควรเขียนif (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
Carson63000

3
ขอบคุณสำหรับการสรุปคำตอบของ Erik ไม่ว่าจะเป็นในทางที่โต้แย้งหรืออารยะน้อย
Kristof Claes

1
"เขียนสิ่งที่ง่ายที่สุดที่ใช้งานได้" เช่น @ Carson63000 เป็นความเรียบง่าย จริงๆแล้วมันอันตรายที่จะคิดอย่างนั้น มันนำไปสู่การพังทลายของ Sudoku TDD ที่น่าอับอาย (google it) เมื่อตามมาด้วยการสุ่มสี่สุ่มห้า TDD นั้นเป็นสิ่งที่ทำให้เกิดความคิดสร้างสรรค์: คุณไม่สามารถสรุปอัลกอริธึมที่ไม่น่าสนใจได้โดยการทำ "สิ่งที่ง่ายที่สุดที่ทำงาน" สุ่มสี่สุ่มห้า "... คุณต้องคิดจริง ๆ ! แต่น่าเสียดายที่บางครั้งโท XP และ TDD ที่ถูกกล่าวหาก็ทำตามมันอย่างสุ่มสี่สุ่มห้า ...
Andres F.

1
@AndresF โปรดทราบว่าความคิดเห็นของคุณปรากฏสูงขึ้นในการค้นหาของ Google มากกว่าความเห็นส่วนใหญ่เกี่ยวกับ "Soduko TDD debacle" หลังจากผ่านไปสามวัน อย่างไรก็ตามวิธีที่จะไม่แก้ปัญหาซูโดกุนั้นสรุปได้: TDD นั้นมีคุณภาพไม่ใช่ความถูกต้อง คุณต้องแก้อัลกอริทึมก่อนเริ่มการเข้ารหัสโดยเฉพาะกับ TDD (ไม่ใช่ว่าฉันไม่ใช่โปรแกรมเมอร์คนแรกที่เขียนโค้ดด้วย)
Mark Hurd

1
pvv.org/~oma/TDDinC_Yahtzee_27oct2011.pdfอาจเป็นที่สนใจ

คำตอบ:


40

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

ความยืดหยุ่นไม่ใช่สำหรับมือใหม่

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

  • การยึดมั่นอย่างเข้มงวดกับกฎหรือแผนการสอน
  • ไม่มีการใช้วิจารณญาณอย่างรอบคอบ

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

นี่เป็นความจริงสำหรับ TDD

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

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

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

ฟังการทดสอบ

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

นี่คือเหตุผลว่าทำไมหนึ่งในข้อความที่สำคัญที่สุดของGOOSคือฟังการทดสอบของคุณ!

ในกรณีของคำถามนี้ปฏิกิริยาแรกของฉันเมื่อเห็น API ที่เสนอของเกม Yahtzee และการอภิปรายเกี่ยวกับ combinatorics ที่สามารถพบได้ในหน้านี้คือว่านี่เป็นข้อเสนอแนะที่สำคัญเกี่ยวกับ API

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

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

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

จากนั้นเมื่อRollชั้นเรียนได้รับการพัฒนาอย่างเพียงพอคุณจะกลับไปที่ SUT ดั้งเดิมแล้วนำออกมาในรูปของRollอินพุต

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

วิธีการวิธีการป้อนข้อมูลและรูปแบบในแง่ของอีกRollกรณีจะแนะนำข้อมูล Builder

สีแดง / สีเขียว / Refactor เป็นกระบวนการสามขั้นตอน

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

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

อยู่ที่นี่ในสถานะที่สามนี้ที่คุณสามารถนำทักษะการทำงานทั้งหมดของคุณมารับได้ ที่นี่คุณได้รับอนุญาตให้สะท้อนรหัส

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

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

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

Rigor เป็นเครื่องมือการเรียนรู้

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

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


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

1
ว้าวขอบคุณ. ฉันกลัวแนวโน้มของผู้คนที่จะบอกผู้เริ่มต้นใน TDD (หรือมีระเบียบวินัย) จริง ๆ ว่า "ไม่ต้องกังวลกับกฎเกณฑ์เพียงทำสิ่งที่รู้สึกดีที่สุด" คุณจะรู้ได้อย่างไรว่าสิ่งใดรู้สึกดีที่สุดเมื่อคุณไม่มีความรู้หรือประสบการณ์ ฉันต้องการจะพูดถึงหลักการจัดลำดับความสำคัญการแปลงหรือรหัสนั้นควรเป็นแบบทั่วไปมากขึ้นเมื่อการทดสอบมีความเฉพาะเจาะจงมากขึ้น ผู้สนับสนุน TDD ที่ตายยากที่สุดอย่างลุงบ๊อบจะไม่ยืนหลังความคิดของ "แค่เพิ่มคำสั่ง if ใหม่สำหรับการทดสอบทุกครั้ง"
sara

41

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

ก่อนอื่นสิ่งที่ง่ายที่สุดที่คุณสามารถทำได้เพื่อให้การทดสอบผ่านได้คือ:

public bool IsFullHouse(int roll1, int roll2, int roll3, int roll4, int roll5)
{
    return true;
}

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

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

public void Returns_true_when_roll_is_full_house()
{
    FullHouseTester sut = new FullHouseTester();
    var actual = sut.IsFullHouse(1, 2, 3, 4, 5);

    Assert.IsFalse(actual);
}

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

public void Returns_true_when_roll_is_full_house()
{
    FullHouseTester sut = new FullHouseTester();
    var actual = sut.IsFullHouse(-1, -2, -3, -4, -5);

    //I dunno - throw exception, return false, etc, whatever you think it should do....
}

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

ปรับปรุง:

ฉันเริ่มสิ่งนี้เป็นความคิดเห็นเพื่อตอบสนองต่อการอัปเดตของคุณ แต่มันเริ่มค่อนข้างยาว ...

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

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

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

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


ฉันได้อัปเดตคำถามของฉันเพื่อเพิ่มข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่ฉันเริ่มต้นด้วยวิธีการตามตัวอักษร
Kristof Claes

9
นี่คือคำตอบที่ดี
tallseth

1
ขอบคุณมากสำหรับคำตอบที่รอบคอบและอธิบายได้ดี จริง ๆ แล้วมันสมเหตุสมผลมากตอนนี้ที่ฉันคิดถึงมัน
Kristof Claes

1
การทดสอบอย่างละเอียดไม่ได้หมายถึงการทดสอบทุก ๆ ชุด ... นั่นมันงี่เง่า สำหรับกรณีนี้ให้นำเรือนเต็มหรือสองและบ้านสองหลังที่ไม่เต็ม นอกจากนี้ยังมีชุดค่าผสมพิเศษที่อาจทำให้เกิดปัญหา (เช่น 5 ชนิด)
Schleis

3
+1 หลักการที่อยู่เบื้องหลังคำตอบนี้อธิบายโดยการเปลี่ยนแปลงการจัดลำดับความสำคัญของ Robert C. Martin cleancoder.posterous.com/the-transformation-priority-premise
Mark Seemann

5

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

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


5

คำตอบของ Erik นั้นยอดเยี่ยม แต่ฉันคิดว่าฉันอาจแบ่งปันเคล็ดลับในการทดสอบการเขียนได้

เริ่มด้วยการทดสอบนี้:

[Test]
public void FullHouseReturnsTrue()
{
    var pairNum = AnyDiceValue();
    var trioNum = AnyDiceValue();

    Assert.That(sut.IsFullHouse(trioNum, pairNum, trioNum, pairNum, trioNum));
}

การทดสอบนี้จะดียิ่งขึ้นถ้าคุณสร้างRollคลาสแทนการส่ง 5 params:

[Test]
public void FullHouseReturnsTrue()
{
    var roll = AnyFullHouse();

    Assert.That(sut.IsFullHouse(roll));
}

ที่ให้การดำเนินการนี้:

public bool IsFullHouse(Roll toCheck)
{
    return true;
}

จากนั้นเขียนบททดสอบนี้:

[Test]
public void StraightReturnsFalse()
{
    var roll = AnyStraight();

    Assert.That(sut.IsFullHouse(roll), Is.False);
}

เมื่อผ่านไปแล้วให้เขียนสิ่งนี้:

[Test]
public void ThreeOfAKindReturnsFalse()
{
    var roll = AnyStraight();

    Assert.That(sut.IsFullHouse(roll), Is.False);
}

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

เห็นได้ชัดว่าใช้วิธีการใดก็ได้ของคุณเพื่อส่งคืนโรลแบบสุ่มที่ตรงตามเกณฑ์ของคุณ

มีประโยชน์เล็กน้อยต่อวิธีการนี้:

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

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

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

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

AnyFullHouse เป็นวิธีการ "ผู้ช่วย" ในกรณีทดสอบ หากเป็นวิธีการทั่วไปก็เพียงพอแล้วที่จะได้รับการทดสอบ!
Mark Hurd

ควรIsFullHouseจริงๆกลับtrueถ้าpairNum == trioNum ?
recursion.ninja

2

ฉันสามารถคิดถึงวิธีการสำคัญสองวิธีที่ฉันจะพิจารณาขณะทำการทดสอบนี้

  1. เพิ่มชุดทดสอบที่ถูกต้อง "บาง" (~ 5) ชุดแบบบ้านที่ถูกต้องและจำนวนที่คาดหวังของเท็จ ({1, 1, 2, 3, 3} เป็นชุดที่ดีโปรดจำไว้ว่าตัวอย่าง 5 ตัวอย่างอาจเป็น ได้รับการยอมรับว่าเป็น "3 ของทั้งคู่บวกคู่" โดยการใช้งานที่ไม่ถูกต้อง) วิธีนี้ถือว่านักพัฒนาซอฟต์แวร์ไม่เพียง แต่พยายามผ่านการทดสอบ แต่ใช้งานจริงถูกต้อง

  2. ทดสอบชุดลูกเต๋าที่เป็นไปได้ทั้งหมด (มี 252 ชุดที่แตกต่างกันเท่านั้น) แน่นอนว่าคุณมีวิธีที่จะรู้ว่าคำตอบที่คาดหวังคืออะไร (ในการทดสอบนี้เรียกว่าoracle.) นี่อาจเป็นการใช้งานอ้างอิงของฟังก์ชั่นเดียวกันหรือมนุษย์ หากคุณต้องการเข้มงวดอย่างจริงจังมันอาจคุ้มค่าที่จะเขียนโค้ดผลลัพธ์ที่คาดหวังด้วยตนเอง

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


คำถามหนึ่งล้านดอลลาร์คือคุณได้รับ Yahtzee AI โดยใช้ TDD บริสุทธิ์หรือไม่? เดิมพันของฉันคือคุณไม่สามารถ; คุณต้องใช้ความรู้เกี่ยวกับโดเมนซึ่งโดยนิยามไม่ได้ตาบอด :)
Andres F.

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

0

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

คุณไม่จำเป็นต้องทดสอบตรรกะแยก 200 ครั้ง คุณสามารถใช้ชุดตัวอย่างเช่น เกือบทุกภาษาการเขียนโปรแกรมมีหนึ่งใน:

Set set;
set.add(a);
set.add(b);
set.add(c);
set.add(d);
set.add(e);

if(set.size() == 2) { // means we *must* be of the form AAAAB or AAABB.
    if(a==b==c==d) // eliminate AAAAB
        return false;
    else
        return true;
}
return false;

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

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