การทดสอบหน่วยของอัลกอริทึมแบบสุ่ม / ไม่กำหนด


41

โครงการปัจจุบันของฉันรวบรัดเกี่ยวข้องกับการสร้าง "เหตุการณ์แบบสุ่ม - จำกัด " ฉันกำลังสร้างตารางการตรวจสอบ บางคนขึ้นอยู่กับข้อ จำกัด ของตารางที่เข้มงวด คุณทำการตรวจสอบสัปดาห์ละครั้งในวันศุกร์เวลา 10:00 น. การตรวจสอบอื่น ๆ คือ "สุ่ม"; มีข้อกำหนดขั้นพื้นฐานที่กำหนดค่าได้เช่น "การตรวจสอบจะต้องเกิดขึ้น 3 ครั้งต่อสัปดาห์", "การตรวจสอบจะต้องเกิดขึ้นระหว่างเวลา 9.00 น. - 21.00 น." และ "ไม่ควรมีการตรวจสอบสองครั้งภายในระยะเวลา 8 ชั่วโมงเดียวกัน" แต่ ภายในข้อ จำกัด ใดก็ตามที่กำหนดค่าไว้สำหรับชุดการตรวจสอบเฉพาะวันที่และเวลาที่เกิดขึ้นไม่ควรคาดเดาได้

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

ใครบ้างมีเคล็ดลับในการออกแบบการทดสอบที่ควรคาดหวังพฤติกรรมที่ไม่กำหนด?


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

ฉันสร้างชุดการทดสอบ "แซนด์บ็อกซ์" ที่มีอัลกอริธึมที่เหมาะสมสำหรับกระบวนการที่ จำกัด (กระบวนการที่อาร์เรย์ไบต์ที่อาจมีความยาวได้กลายเป็นความยาวระหว่างนาทีและสูงสุด) จากนั้นฉันเรียกใช้รหัสนั้นผ่าน FOR วงวนที่ให้อัลกอริธึมที่รู้จักกันหลายไบต์อาร์เรย์ (ค่าจาก 1 ถึง 10,000,000 เพิ่งเริ่มต้น) และมีอัลกอริทึม จำกัด แต่ละค่าเป็น 1009 และ 7919 (ฉันใช้ตัวเลขเฉพาะเพื่อให้แน่ใจว่า อัลกอริทึมจะไม่ผ่าน GCF บางช่วงระหว่างอินพุตและช่วงเอาต์พุต) ค่าที่ จำกัด ที่เป็นผลลัพธ์จะถูกนับและฮิสโตแกรมที่สร้างขึ้น ถึง "ผ่าน" อินพุตทั้งหมดจะต้องสะท้อนให้เห็นภายในฮิสโตแกรม (สติปัญญาเพื่อให้แน่ใจว่าเราจะไม่ "แพ้" ใด ๆ ) และความแตกต่างระหว่างถังสองอันในฮิสโตแกรมไม่ควรมากกว่า 2 (จริง ๆ ควร <= 1 , แต่คอยติดตาม) อัลกอริทึมที่ชนะถ้ามีสามารถตัดและวางโดยตรงในรหัสการผลิตและการทดสอบถาวรสำหรับการถดถอย

นี่คือรหัส:

    private void TestConstraintAlgorithm(int min, int max, Func<byte[], long, long, long> constraintAlgorithm)
    {
        var histogram = new int[max-min+1];
        for (int i = 1; i <= 10000000; i++)
        {
            //This is the stand-in for the PRNG; produces a known byte array
            var buffer = BitConverter.GetBytes((long)i);

            long result = constraintAlgorithm(buffer, min, max);

            histogram[result - min]++;
        }

        var minCount = -1;
        var maxCount = -1;
        var total = 0;
        for (int i = 0; i < histogram.Length; i++)
        {
            Console.WriteLine("{0}: {1}".FormatWith(i + min, histogram[i]));
            if (minCount == -1 || minCount > histogram[i])
                minCount = histogram[i];
            if (maxCount == -1 || maxCount < histogram[i])
                maxCount = histogram[i];
            total += histogram[i];
        }

        Assert.AreEqual(10000000, total);
        Assert.LessOrEqual(maxCount - minCount, 2);
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionMSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByMSBRejection);
    }

    private long ConstrainByMSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length-1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;
        //Apply a bitmask to the value, removing the MSB on each loop until it falls in the range.
        var mask = long.MaxValue;
        while (result > max - min)
        {
            mask >>= 1;
            result &= mask;
        }
        result += min;

        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionLSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByLSBRejection);
    }

    private long ConstrainByLSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length - 1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;

        //Bit-shift the number 1 place to the right until it falls within the range
        while (result > max - min)
            result >>= 1;

        result += min;
        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionModulus()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByModulo);
    }

    private long ConstrainByModulo(byte[] buffer, long min, long max)
    {
        buffer[buffer.Length - 1] &= 0x7f;
        var result = BitConverter.ToInt64(buffer, 0);

        //Modulo divide the value by the range to produce a value that falls within it.
        result %= max - min + 1;

        result += min;
        return result;
    }

... และนี่คือผลลัพธ์:

ป้อนคำอธิบายรูปภาพที่นี่

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

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

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


2
ดังนั้นในที่สุดคุณต้องการโปรแกรมที่ดูผลลัพธ์ของตัวสร้างตัวเลขสุ่มและตัดสินใจว่ามันสุ่ม? ในขณะที่ "5,4,10,31,120,390,2,3,4" เป็นแบบสุ่ม แต่ "49,39,1,10,103,12,4,189" ก็ไม่ได้?
psr

ไม่ แต่หลีกเลี่ยงการแนะนำอคติระหว่าง PRNG จริงและผลลัพธ์ที่ได้จะดี
KeithS

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

คุณควรทดสอบการรวมกัน การมีการตรวจสอบที่คาดหวังอย่างเท่าเทียมกันต่อชั่วโมงจะไม่ป้องกันในกรณีที่การตรวจสอบ 11:00 น. ในวันอังคารจะตามด้วย 14.00 น. ทุกวันพฤหัสบดีและ 10:00 ในวันศุกร์
David Thornley

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

คำตอบ:


17

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

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

โดยทั่วไปฉันมีวัตถุจำลองสำหรับข้อมูลที่ไม่สามารถกำหนดได้หรือไม่แน่นอนทุกชนิด (ในเวลาที่เขียนการทดสอบ) รวมถึงเครื่องกำเนิดไฟฟ้า GUID และ DateTime.Now

แก้ไขจากความคิดเห็น: คุณต้องเยาะเย้ย PRNG (คำนั้นหนีฉันเมื่อคืน) ที่ระดับต่ำสุดที่เป็นไปได้ - เช่น เมื่อมันสร้างอาร์เรย์ของไบต์ไม่ใช่หลังจากที่คุณเปลี่ยนเป็น Int64s หรือแม้แต่ในทั้งสองระดับดังนั้นคุณสามารถทดสอบการแปลงเป็นอาร์เรย์ของ Int64 ได้ตามที่ต้องการแล้วทดสอบแยกกันว่าการแปลงเป็นอาร์เรย์ของ DateTimes นั้นทำงานได้ตามที่ต้องการ ดังที่ Jonathon กล่าวคุณสามารถทำได้โดยให้เมล็ดชุดหรือคุณสามารถส่งกลับอาร์เรย์จำนวนไบต์

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

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


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

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

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

การวัดทางสถิติอย่างถูกต้องของลำดับแบบสุ่ม (สหสัมพันธ์บล็อกความสัมพันธ์ค่าเฉลี่ยค่าเบี่ยงเบนมาตรฐาน ฯลฯ ) จะล้มเหลวในการตอบสนองความคาดหวังของคุณเฉพาะในกรณีที่คุณทำตัวอย่างขนาดเล็กมากเท่านั้น เพิ่มชุดการสุ่มตัวอย่างและ / หรือเพิ่มแถบข้อผิดพลาดที่อนุญาต
lurscher

1
"อคติบางอย่างแม้ในระดับนั้น" ถ้าคุณใช้ PRNG ที่ดีแล้วคุณจะไม่สามารถหาการทดสอบใด ๆ (ด้วยขอบเขตการคำนวณที่เหมือนจริง) ที่สามารถบอกได้ว่าแตกต่างจากการสุ่มจริง ดังนั้นในทางปฏิบัติเราสามารถสรุปได้ว่า PRNG ที่ดีไม่มีอคติใด ๆ
CodesInChaos

23

นี่จะฟังดูเหมือนคำตอบงี่เง่า แต่ฉันจะขว้างมันออกไปเพราะนี่คือสิ่งที่ฉันเคยเห็นมาก่อน:

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

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


เผง: ถ้าคุณไม่สามารถทดสอบองค์ประกอบของอัลกอริทึมให้ย่อมันออกมาและเยาะเย้ยมันได้
Steve Greatrex

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

เกี่ยวกับวิธีการ "ตารางสุ่ม" คุณยังสามารถใช้การทดสอบการใช้งานที่มีอัลกอริทึมการสร้างตัวเลขที่ "ย้อนกลับได้" สิ่งนี้ช่วยให้คุณสามารถ "กรอ" PRNG หรือแม้แต่เคียวรีเพื่อดูว่า N เอาต์พุตล่าสุดคืออะไร มันจะช่วยให้การดีบักในเชิงลึกมากขึ้นในบางสถานการณ์
Darien

นี่ไม่ใช่เรื่องโง่ - เป็นวิธีเดียวกับที่ Google ใช้ในการทำซ้ำการฉีดล้มเหลวในการทดสอบ Spanner ตามกระดาษ :)
Akshat Mahajan

6

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

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

บทความ Wikipedia http://en.wikipedia.org/wiki/Randomness_testsและลิงก์ติดตามผลมีข้อมูลเพิ่มเติม


แม้แต่ PRNG ที่ปานกลางจะไม่แสดง patters ใด ๆ ในการทดสอบทางสถิติ สำหรับ PRNG ที่ดีมันเป็นไปไม่ได้ที่จะแยกพวกมันออกจากการสุ่มตัวเลขจริง
CodesInChaos

4

ฉันมีสองคำตอบสำหรับคุณ

=== คำตอบแรก ===

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

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

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

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


=== คำตอบที่สอง ===

ว้าว ... เป็นปัญหาที่ยอดเยี่ยม!

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

  1. คุณสามารถใช้กำลังดุร้าย เพียงใช้อัลกอริธึมเป็นจำนวนเท่าของเวลาโดยใช้ RNG ที่แท้จริงเป็นอินพุต ตรวจสอบผลลัพธ์ที่ส่งออกเพื่อดูว่าพวกเขามีการกระจายอย่างสม่ำเสมอ การทดสอบของคุณจะต้องล้มเหลวถ้าการกระจายแตกต่างจากชุดที่สมบูรณ์แบบโดยมากกว่าหนึ่ง threshhold และเพื่อให้แน่ใจว่าคุณพบปัญหา Threshhold ไม่สามารถตั้งค่าต่ำเกินไป นั่นหมายความว่าคุณจะต้องมีจำนวนมากของการทำงานเพื่อให้แน่ใจว่าความน่าจะเป็นของการบวกผิดพลาด (การทดสอบล้มเหลวโดยบังเอิญแบบสุ่ม) มีขนาดเล็กมาก (<1% น้อยสำหรับฐานรหัสขนาดกลางแม้แต่น้อยสำหรับ ฐานรหัสขนาดใหญ่)

  2. พิจารณาอัลกอริทึมของคุณเป็นฟังก์ชั่นที่นำเอาการเชื่อมต่อของเอาท์พุท RNG ทั้งหมดเป็นอินพุทจากนั้นสร้างเวลาตรวจสอบเป็นเอาท์พุท หากคุณรู้ว่าฟังก์ชั่นนี้ต่อเนื่องเป็นชิ้น ๆ มีวิธีทดสอบคุณสมบัติของคุณ แทนที่ RNG ด้วย RNG ที่เยาะเย้ยและเรียกใช้อัลกอริทึมหลาย ๆ ครั้งเพื่อสร้างเอาต์พุต RNG ที่กระจายอย่างสม่ำเสมอ ดังนั้นหากรหัสของคุณต้องการการโทร 2 RNG แต่ละครั้งอยู่ในช่วง [0..1] คุณอาจมีการทดสอบรันอัลกอริทึม 100 ครั้งส่งคืนค่า [(0.0,0.0), (0.0,0.1), (0.0,0.1) 0.2), ... (0.0,0.9), (0.1,0.0), (0.1,0.1), ... (0.9,0.9)] จากนั้นคุณสามารถตรวจสอบได้ว่าผลลัพธ์ของการวิ่ง 100 ครั้งนั้นถูกแจกจ่ายอย่างสม่ำเสมอในช่วงที่อนุญาตหรือไม่

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


3

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

คุณไม่ต้องการให้หน่วยทดสอบของคุณมองข้ามเช่นเกิดข้อผิดพลาดที่เกิดขึ้นเมื่อ Random.nextInt (1000) ส่งคืนค่า 0 หรือ 999


3

คุณอาจต้องการที่จะดูที่ Sevcikova et al: "การทดสอบอัตโนมัติของระบบ Stochastic: A วิธีการลงดินที่มีสถิติเชิงสถิติ" ( PDF )

วิธีการถูกนำไปใช้ในกรณีทดสอบต่างๆสำหรับแพลตฟอร์มการจำลองUrbanSim


นั่นคือสิ่งที่ดีมี
KeithS

2

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

แนวทางของฉันจะเป็น:

  1. ตรวจสอบ (หรือรับตามที่กำหนด) ว่าแหล่ง PRNG นั้นสุ่มเพียงพอ (โดยใช้มาตรการทางสถิติมาตรฐาน)
  2. ตรวจสอบว่าการแปลง PRNG-to-datetime ที่ไม่มีข้อ จำกัด นั้นสุ่มอย่างเพียงพอบนพื้นที่เอาต์พุต (นี่เป็นการตรวจสอบการขาดอคติในการแปลง) การทดสอบความสม่ำเสมอของคำสั่งแรกที่เรียบง่ายของคุณน่าจะเพียงพอแล้วที่นี่
  3. ตรวจสอบว่ากรณีที่มีข้อ จำกัด นั้นมีความสม่ำเสมอเพียงพอ (การทดสอบความสม่ำเสมอลำดับแรกอย่างง่ายเหนือถังขยะที่ถูกต้อง)

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

ตามลักษณะของอัลกอริทึมการแปลง / ข้อ จำกัด :

ป.ร. ให้ไว้:วิธีการสร้างค่าหลอกสุ่มpโดยที่ 0 <= p <= M

ต้องการ:เอาต์พุตyใน (อาจไม่ต่อเนื่อง) ช่วง 0 <= y <= N <= M

ขั้นตอนวิธีการ:

  1. คำนวณr = floor(M / N)นั่นคือจำนวนช่วงเอาท์พุทที่สมบูรณ์ที่พอดีกับช่วงอินพุต
  2. คำนวณค่าสูงสุดที่ยอมรับได้สำหรับp :p_max = r * N
  3. สร้างค่าสำหรับหน้าจนกว่าจะมีค่าน้อยกว่าหรือเท่ากับp_maxพบ
  4. คำนวณ y = p / r
  5. ถ้าyเป็นที่ยอมรับคืนมันมิฉะนั้นทำซ้ำขั้นตอนที่ 3

กุญแจสำคัญคือการทิ้งค่าที่ยอมรับไม่ได้มากกว่าการพับแบบไม่สม่ำเสมอ

ในรหัสหลอก:

# assume prng generates non-negative values
def randomInRange(min, max, prng):
    range = max - min
    factor = prng.max / range

    do:
        value = prng()
    while value > range * factor
    return (value / factor) + min

def constrainedRandom(constraint, prng):
    do:
        value = randomInRange(constraint.min, constraint.max, prng)
    while not constraint.is_acceptable(value)

1

นอกเหนือจากการตรวจสอบว่าโค้ดของคุณไม่ได้ล้มเหลวหรือส่งข้อยกเว้นที่ถูกต้องในตำแหน่งที่ถูกต้องคุณสามารถสร้างคู่อินพุต / การตอบสนองที่ถูกต้อง (แม้จะคำนวณด้วยตนเอง) ป้อนฟีดในการทดสอบและตรวจสอบให้แน่ใจว่า ไม่ดี แต่นั่นก็เป็นสิ่งที่คุณทำได้ อย่างไรก็ตามในกรณีของคุณมันไม่ได้สุ่มจริงๆเมื่อคุณสร้างตารางเวลาของคุณคุณสามารถทดสอบเพื่อให้สอดคล้องกับกฎ - ต้องมีการตรวจสอบ 3 ครั้งต่อสัปดาห์ระหว่าง 9-9; ไม่มีความต้องการหรือความสามารถในการทดสอบตามเวลาที่แน่นอนเมื่อเกิดการตรวจสอบ


1

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


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

@DawoodibnKareem ที่มีขนาดตัวอย่างเพียงพอ (และข้อ จำกัด ที่เหมาะสมกับความสม่ำเสมอ) คุณสามารถลดโอกาสที่การทดสอบจะล้มเหลวเป็น 1 ในพันล้าน และโดยทั่วไปแล้วสถิติเช่นนี้จะเป็นเลขชี้กำลังด้วย n ดังนั้นจึงใช้เวลาน้อยกว่าที่คุณคาดหวังไว้ในตัวเลข
mbrig

1

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

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


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

0

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


0

กรณีนี้ดูเหมือนว่าเหมาะสำหรับสถานที่ให้บริการการทดสอบตาม

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

นี่คือตัวอย่าง (โง่, ล้มเหลว) ทดสอบsumฟังก์ชั่น:

@given(lists(floats()))
def test_sum(alist):
    result = sum(alist)
    assert isinstance(result, float)
    assert result > 0
  • กรอบการทดสอบจะสร้างหนึ่งรายการในเวลา
  • เนื้อหาของรายการจะเป็นเลขทศนิยม
  • sum ถูกเรียกและคุณสมบัติของผลลัพธ์ได้รับการตรวจสอบความถูกต้อง
  • ผลลอยเสมอ
  • ผลลัพธ์เป็นค่าบวก

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

  • sum([]) is 0 (int ไม่ใช่ลอย)
  • sum([-0.9]) เป็นลบ
  • sum([0.0]) ไม่ใช่บวกอย่างเด็ดขาด
  • sum([..., nan]) is nan ซึ่งไม่เป็นบวก

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

ในกรณี OP คุณสมบัติที่ตรวจสอบจะมีความซับซ้อนมากขึ้น: ประเภทการตรวจสอบปัจจุบันการตรวจสอบประเภทสามครั้งต่อสัปดาห์เวลาการตรวจสอบ B เสมอที่ 12:00 ประเภทการตรวจสอบ C จาก 9 ถึง 9, [กำหนดตารางเวลาเป็นสัปดาห์] การตรวจสอบประเภท A, B, C มีอยู่ทั้งหมด ฯลฯ

ห้องสมุดที่รู้จักกันดีคือQuickCheck for Haskell ดูหน้า Wikipedia ด้านล่างเพื่อดูรายการของห้องสมุดดังกล่าวในภาษาอื่น:

https://en.wikipedia.org/wiki/QuickCheck

สมมติฐาน (สำหรับ Python) มีบทความที่ดีเกี่ยวกับการทดสอบประเภทนี้:

https://hypothesis.works/articles/what-is-property-based-testing/


-1

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

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


-1

เป้าหมายของคุณคือไม่เขียนการทดสอบหน่วยและผ่านการทดสอบ แต่เพื่อให้แน่ใจว่าโปรแกรมของคุณตรงกับความต้องการ วิธีเดียวที่คุณสามารถทำได้คือกำหนดความต้องการของคุณอย่างแม่นยำตั้งแต่แรก ตัวอย่างเช่นคุณพูดถึง "การตรวจสอบสามสัปดาห์ในเวลาสุ่ม" ฉันบอกว่าข้อกำหนดคือ: (a) การตรวจสอบ 3 ครั้ง (ไม่ใช่ 2 หรือ 4), (b) ในบางครั้งที่ไม่สามารถคาดเดาได้โดยคนที่ไม่ต้องการถูกตรวจสอบโดยไม่คาดคิดและ (c) ไม่ใกล้กันเกินไป - การตรวจสอบสองครั้งห่างกันห้านาทีนั้นอาจไร้ประโยชน์

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

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


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