วิธีทำ TDD สำหรับบางอย่างที่มีการเรียงสับเปลี่ยนมากมาย?


15

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

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


1
ความดีโดยรวมของระบบ AI มักจะวัดจากการทดสอบการเรียกคืนความแม่นยำด้วยชุดอินพุตมาตรฐาน การทดสอบนี้มีความคล้ายคลึงกับ "การทดสอบการรวม" ดังที่คนอื่น ๆ พูดถึงมันเป็นเหมือน "การวิจัยอัลกอริธึมที่ขับเคลื่อนด้วยการทดสอบ" มากกว่า "การออกแบบที่ขับเคลื่อนด้วยการทดสอบ"
rwong

โปรดระบุความหมายของ "AI" มันเป็นสาขาวิชามากกว่าหลักสูตรประเภทใดประเภทหนึ่งโดยเฉพาะ สำหรับการใช้งาน AI บางอย่างโดยทั่วไปคุณไม่สามารถทดสอบสิ่งต่าง ๆ (เช่น: พฤติกรรมที่เกิดขึ้น) ผ่านทาง TDD
Steven Evers

@SnOrfus ฉันหมายถึงมันโดยทั่วไปแล้วเป็นเครื่องมือในการตัดสินใจ
นิโคล

คำตอบ:


7

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

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

class Decider {

  public boolean decide(float input, float risk) {

      float inputRand = Math.random();
      if (inputRand > input) {
         float riskRand = Math.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider();
d.decide(0.1337f, 0.1337f);

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

public interface IRandom {

   public float random();

}

public class ConcreteRandom implements IRandom {

   public float random() {
      return Math.random();
   }

}

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

class Decider {

  IRandom irandom;

  public Decider(IRandom irandom) { // constructor injection
      this.irandom = irandom;
  }

  public boolean decide(float input, float risk) {

      float inputRand = irandom.random();
      if (inputRand > input) {
         float riskRand = irandom.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider(new ConcreteRandom);
d.decide(0.1337f, 0.1337f);

คุณอาจถามตัวเองว่าทำไม "การขยายโค้ด" นี้จึงเป็นสิ่งจำเป็น สำหรับผู้เริ่มต้นตอนนี้คุณสามารถเยาะเย้ยพฤติกรรมของส่วนที่สุ่มของอัลกอริทึมเพราะDeciderตอนนี้มีการพึ่งพาที่เป็นไปตามIRandomสัญญา "s" คุณสามารถใช้กรอบการเยาะเย้ยสำหรับสิ่งนี้ แต่ตัวอย่างนี้ง่ายพอที่จะให้รหัสตัวเอง:

class MockedRandom() implements IRandom {

    public List<Float> floats = new ArrayList<Float>();
    int pos;

   public void addFloat(float f) {
     floats.add(f);
   }

   public float random() {
      float out = floats.get(pos);
      if (pos != floats.size()) {
         pos++;
      }
      return out;
   }

}

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

@Before void setUp() {
  MockedRandom mRandom = new MockedRandom();

  Decider decider = new Decider(mRandom);
}

@Test
public void testDecisionWithLowInput_ShouldGiveFalse() {

  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandButLowRiskRand_ShouldGiveFalse() {

  mRandom.addFloat(1f);
  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandAndHighRiskRand_ShouldGiveTrue() {

  mRandom.addFloat(1f);
  mRandom.addFloat(1f);

  assertTrue(decider.decide(0.1337f, 0.1337f));
}

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


3

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

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


2

TDD ไม่ได้เกี่ยวกับการทดสอบ แต่เป็นการออกแบบ

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

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

แก้ไข: ฉันต้องการเพิ่มตัวอย่าง แต่ไม่มีเวลาก่อนหน้านี้

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

หรือเราจัดการปัญหาได้สี่ส่วน:

  1. สำรวจอาร์เรย์
  2. เปรียบเทียบรายการที่เลือก
  3. สลับรายการ
  4. ประสานงานทั้งสามด้านบน

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

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

ที่สามนั้นง่ายต่อการทดสอบอย่างไม่น่าเชื่อ

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

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

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


1

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

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


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

0

ใช้ขอบเคสบวกอินพุตแบบสุ่มบางส่วน

ตัวอย่างการเรียงลำดับ:

  • เรียงลำดับรายการแบบสุ่มไม่กี่รายการ
  • ใช้รายการที่เรียงลำดับแล้ว
  • ทำรายการที่อยู่ในลำดับกลับกัน
  • ใช้รายการที่เกือบจะเรียงลำดับ

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

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