Kiểm thử đơn vị các thuật toán ngẫu nhiên / không xác định vốn có


41

Dự án hiện tại của tôi, ngắn gọn, liên quan đến việc tạo ra "các sự kiện ngẫu nhiên ngẫu nhiên". Về cơ bản tôi đang tạo ra một lịch trình kiểm tra. Một số trong số họ được dựa trên các ràng buộc lịch trình nghiêm ngặt; bạn thực hiện kiểm tra mỗi tuần một lần vào thứ Sáu lúc 10:00 sáng. Kiểm tra khác là "ngẫu nhiên"; có các yêu cầu cấu hình cơ bản như "kiểm tra phải diễn ra 3 lần mỗi tuần", "kiểm tra phải diễn ra trong khoảng thời gian từ 9 giờ sáng đến 9 giờ tối" và "không nên có hai lần kiểm tra trong cùng khoảng thời gian 8 giờ", nhưng trong bất kỳ ràng buộc nào được định cấu hình cho một bộ kiểm tra cụ thể, ngày và thời gian kết quả sẽ không thể dự đoán được.

Các thử nghiệm đơn vị và TDD, IMO, có giá trị lớn trong hệ thống này vì chúng có thể được sử dụng để tăng dần nó trong khi toàn bộ các yêu cầu của nó vẫn chưa hoàn thành và đảm bảo rằng tôi không "quá kỹ thuật" để làm những việc tôi không làm Hiện tại tôi biết tôi cần. Lịch trình nghiêm ngặt là một miếng bánh cho TDD. Tuy nhiên, tôi cảm thấy rất khó để xác định những gì tôi đang kiểm tra khi tôi viết các bài kiểm tra cho phần ngẫu nhiên của hệ thống. Tôi có thể khẳng định rằng tất cả các thời gian được tạo bởi bộ lập lịch phải nằm trong các ràng buộc, nhưng tôi có thể thực hiện một thuật toán vượt qua tất cả các thử nghiệm như vậy mà không có thời gian thực tế là rất "ngẫu nhiên". Trong thực tế đó chính xác là những gì đã xảy ra; Tôi đã tìm thấy một vấn đề trong đó thời gian, mặc dù không thể dự đoán chính xác, rơi vào một tập hợp nhỏ của phạm vi ngày / thời gian cho phép. Thuật toán vẫn vượt qua tất cả các xác nhận mà tôi cảm thấy có thể đưa ra một cách hợp lý và tôi không thể thiết kế một thử nghiệm tự động sẽ thất bại trong tình huống đó, nhưng vượt qua khi cho kết quả "ngẫu nhiên hơn". Tôi đã phải chứng minh vấn đề đã được giải quyết bằng cách cơ cấu lại một số thử nghiệm hiện có để lặp lại một số lần và kiểm tra trực quan rằng thời gian được tạo ra có nằm trong phạm vi cho phép đầy đủ hay không.

Có ai có bất cứ lời khuyên để thiết kế các bài kiểm tra nên mong đợi hành vi không xác định?


Cảm ơn mọi lời gợi ý. Ý kiến ​​chính dường như là tôi cần một bài kiểm tra xác định để có được kết quả xác định, có thể lặp lại, có thể khẳng định . Có ý nghĩa.

Tôi đã tạo ra một tập hợp các bài kiểm tra "hộp cát" có chứa các thuật toán ứng cử viên cho quy trình ràng buộc (quá trình mà một mảng byte có thể dài trở thành một khoảng dài giữa một phút và tối đa). Sau đó, tôi chạy mã đó thông qua một vòng lặp FOR cung cấp cho thuật toán một số mảng byte đã biết (các giá trị từ 1 đến 10.000.000 chỉ để bắt đầu) và có thuật toán ràng buộc từng giá trị trong khoảng từ 1009 đến 7919 (Tôi đang sử dụng các số nguyên tố để đảm bảo thuật toán sẽ không vượt qua một số GCF ngẫu nhiên giữa các phạm vi đầu vào và đầu ra). Các giá trị ràng buộc kết quả được tính và biểu đồ được tạo ra. Để "vượt qua", tất cả các yếu tố đầu vào phải được phản ánh trong biểu đồ (sự tỉnh táo để đảm bảo chúng tôi không "mất" bất kỳ) và chênh lệch giữa hai nhóm trong biểu đồ không thể lớn hơn 2 (thực sự phải là <= 1 , nhưng hãy theo dõi). Thuật toán chiến thắng, nếu có, có thể được cắt và dán trực tiếp vào mã sản xuất và thử nghiệm vĩnh viễn được đưa ra để hồi quy.

Đây là mã:

    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;
    }

... Và đây là kết quả:

nhập mô tả hình ảnh ở đây

Từ chối LSB (thay đổi số bit cho đến khi nó nằm trong phạm vi) là TERRIBLE, vì một lý do rất dễ giải thích; khi bạn chia bất kỳ số nào cho 2 cho đến khi nó nhỏ hơn một mức tối đa, bạn sẽ thoát ngay khi có và với bất kỳ phạm vi không tầm thường nào, điều đó sẽ làm sai lệch kết quả về phía trên thứ ba (như đã thấy trong kết quả chi tiết của biểu đồ ). Đây chính xác là hành vi tôi thấy từ những ngày kết thúc; tất cả thời gian là vào buổi chiều, vào những ngày rất cụ thể.

Từ chối MSB (loại bỏ bit đáng kể nhất ra khỏi số một tại một thời điểm cho đến khi nó nằm trong phạm vi) sẽ tốt hơn, nhưng một lần nữa, vì bạn đang cắt các số rất lớn với mỗi bit, nó không được phân bổ đều; bạn không có khả năng nhận được số ở đầu trên và dưới, vì vậy bạn có xu hướng về thứ ba giữa. Điều đó có thể có lợi cho ai đó đang tìm cách "bình thường hóa" dữ liệu ngẫu nhiên thành một đường cong hình chuông, nhưng tổng hai hoặc nhiều số ngẫu nhiên nhỏ hơn (tương tự như ném xúc xắc) sẽ cho bạn đường cong tự nhiên hơn. Đối với mục đích của tôi, nó thất bại.

Người duy nhất vượt qua bài kiểm tra này là hạn chế phân chia modulo, điều này cũng hóa ra là nhanh nhất trong ba. Modulo, theo định nghĩa của nó, sẽ tạo ra một bản phân phối càng nhiều càng tốt với các đầu vào khả dụng.


2
Vì vậy, cuối cùng, bạn muốn một chương trình xem ra đầu ra của một trình tạo số ngẫu nhiên và quyết định xem nó có ngẫu nhiên không? Như trong "5,4,10,31,120,390,2,3,4" là ngẫu nhiên nhưng "49,39,1,10,103,12,4,189" thì không?
psr

Không, nhưng tránh giới thiệu sai lệch giữa PRNG thực tế và kết quả cuối cùng sẽ tốt đẹp.
KeithS

Sau đó, có vẻ như chế nhạo PRNG sẽ ổn. Bạn không cần các giá trị ngẫu nhiên thực tế để kiểm tra rằng bạn không sử dụng các giá trị. Nếu bạn có một lỗi ép các giá trị ngẫu nhiên thành một tập hợp con quá nhỏ trong phạm vi cho phép, bạn phải nhận được một số giá trị cụ thể sai.
psr

Bạn cũng nên kiểm tra kết hợp. Có các cuộc kiểm tra dự kiến ​​gần như bằng nhau mỗi giờ sẽ không bảo vệ chống lại trường hợp, giả sử, cuộc kiểm tra 11 giờ sáng Thứ Ba luôn được theo sau bởi 2 giờ chiều vào Thứ Năm và 10 giờ sáng Thứ Sáu.
David Thornley

Đó là một thử nghiệm của chính PRNG; thử nghiệm của (các) cơ chế ràng buộc, như được cấu trúc ở trên, sẽ luôn thất bại trong thử nghiệm như vậy bởi vì nó được cung cấp một bộ dữ liệu hoàn toàn không ngẫu nhiên. Giả sử cơ chế ràng buộc không nỗ lực để "đặt hàng" dữ liệu ngẫu nhiên mà tôi gọi đó là "kiểm tra bên ngoài", đây là điều mà một bài kiểm tra đơn vị không nên làm.
KeithS

Câu trả lời:


17

Những gì bạn thực sự muốn kiểm tra ở đây, tôi giả sử, là được đưa ra một tập hợp kết quả cụ thể từ bộ ngẫu nhiên, phần còn lại của phương thức của bạn thực hiện chính xác.

Nếu đó là những gì bạn đang tìm kiếm thì hãy chế nhạo ngẫu nhiên, để làm cho nó có tính quyết định trong các lĩnh vực thử nghiệm.

Tôi thường có các đối tượng giả cho tất cả các loại dữ liệu không xác định hoặc không dự đoán được (tại thời điểm viết bài kiểm tra), bao gồm các trình tạo GUID và DateTime.Now.

Chỉnh sửa, từ các bình luận: Bạn phải chế nhạo PRNG (thuật ngữ đó đã thoát khỏi tôi đêm qua) ở mức thấp nhất có thể - tức là. khi nó tạo ra các mảng byte, không phải sau khi bạn biến chúng thành Int64. Hoặc thậm chí ở cả hai cấp độ, vì vậy bạn có thể kiểm tra chuyển đổi của mình sang một mảng Int64 hoạt động như dự định và sau đó kiểm tra riêng rằng chuyển đổi của bạn sang một mảng DateTimes hoạt động như dự định. Như Jonathon đã nói, bạn chỉ có thể làm điều đó bằng cách cung cấp cho nó một hạt giống đã đặt hoặc bạn có thể cung cấp cho nó mảng byte để trả về.

Tôi thích cái thứ hai hơn vì nó sẽ không bị hỏng nếu việc triển khai khung PRNG thay đổi. Tuy nhiên, một lợi thế để cung cấp cho nó hạt giống là nếu bạn tìm thấy một trường hợp trong sản xuất không hoạt động như dự định, bạn chỉ cần đăng nhập một số để có thể sao chép nó, trái ngược với toàn bộ mảng.

Tất cả điều này đã nói, bạn phải nhớ rằng nó được gọi là Trình tạo số ngẫu nhiên giả vì một lý do. Có thể có một số sai lệch ngay cả ở cấp độ đó.


1
Không. Điều tôi muốn kiểm tra trong trường hợp này là chính bộ ngẫu nhiên hóa và khẳng định rằng các giá trị "ngẫu nhiên" được tạo bởi bộ ngẫu nhiên nằm trong các ràng buộc đã chỉ định trong khi vẫn là "ngẫu nhiên", vì không thiên về phân phối không đồng đều trên mức cho phép khoảng thời gian. Tôi có thể và thực hiện kiểm tra một cách xác định rằng một ngày / thời gian cụ thể vượt qua chính xác hoặc không vượt qua một ràng buộc cụ thể nào, nhưng vấn đề thực sự tôi gặp phải là ngày được tạo ra bởi bộ ngẫu nhiên và do đó có thể dự đoán được.
KeithS

Điều duy nhất tôi có thể nghĩ đến là để bộ ngẫu nhiên phun ra một loạt các ngày và tạo ra một biểu đồ, sau đó khẳng định rằng các giá trị được phân phối tương đối đồng đều. Điều đó có vẻ rất nặng tay và vẫn không mang tính quyết định vì bất kỳ tập hợp dữ liệu thực sự ngẫu nhiên nào cũng có thể cho thấy sự thiên vị rõ ràng rằng một tập hợp lớn hơn sau đó sẽ bác bỏ.
KeithS

1
Đó là một bài kiểm tra sẽ thỉnh thoảng phá vỡ và không thể đoán trước. Bạn không muốn điều đó. Tôi nghĩ rằng bạn đã hiểu sai quan điểm của tôi, thành thật mà nói. Ở đâu đó bên trong những gì bạn đang gọi là ngẫu nhiên, phải có một dòng mã tạo ra một số ngẫu nhiên, phải không? Dòng đó là những gì tôi đang đề cập đến như một ngẫu nhiên, và phần còn lại của những gì bạn đang gọi là ngẫu nhiên (phân phối ngày dựa trên dữ liệu "ngẫu nhiên") là những gì bạn muốn kiểm tra. Hay tôi đang thiếu một cái gì đó?
pdr

số liệu thống kê chính xác của các chuỗi ngẫu nhiên (tương quan, tương quan khối, trung bình, độ lệch chuẩn, v.v.) sẽ chỉ không đáp ứng phạm vi mong đợi của bạn chỉ khi bạn thực hiện các mẫu thực sự nhỏ. Tăng bộ lấy mẫu của bạn và / hoặc tăng các thanh lỗi được phép của bạn
lurscher

1
"Một số sai lệch ngay cả ở mức đó" Nếu bạn sử dụng PRNG tốt, thì bạn sẽ không thể tìm thấy bất kỳ thử nghiệm nào (với giới hạn tính toán thực tế) có thể phân biệt nó với sự ngẫu nhiên thực sự. Vì vậy, trong thực tế, người ta có thể cho rằng một PRNG tốt không có thành kiến ​​gì.
CodeInChaos

23

Điều này nghe có vẻ như là một câu trả lời ngu ngốc, nhưng tôi sẽ ném nó ra khỏi đó bởi vì đây là cách tôi đã thấy nó được thực hiện trước đây:

Tách mã của bạn khỏi PRNG - chuyển hạt giống ngẫu nhiên vào tất cả các mã sử dụng ngẫu nhiên. Sau đó, bạn có thể xác định các giá trị 'làm việc' từ một hạt giống (hoặc nhiều hạt giống sẽ giúp bạn cảm thấy tốt hơn). Điều này sẽ cung cấp cho bạn khả năng kiểm tra đầy đủ mã của bạn mà không cần phải dựa vào luật số lượng lớn.

Nghe có vẻ điên rồ, nhưng đây là cách quân đội thực hiện (hoặc là họ sử dụng một 'bảng ngẫu nhiên' hoàn toàn không ngẫu nhiên)


Chính xác: nếu bạn không thể kiểm tra một yếu tố của thuật toán, hãy tóm tắt nó và chế nhạo nó
Steve Greatrex

Tôi đã không xác định một giá trị hạt giống xác định; thay vào đó, tôi đã loại bỏ hoàn toàn yếu tố "ngẫu nhiên" để tôi thậm chí không phải dựa vào thuật toán PRNG cụ thể. Tôi đã có thể kiểm tra rằng, với một phạm vi lớn các số phân bố đồng đều, thuật toán tôi đã sử dụng có thể giới hạn các số đó đến một phạm vi nhỏ hơn mà không đưa ra sai lệch. Bản thân PRNG cần được kiểm tra đầy đủ bởi bất kỳ ai đã phát triển nó (Tôi đang sử dụng RNGCryptoServiceProvider).
KeithS

Về phương pháp "bảng ngẫu nhiên", bạn cũng có thể sử dụng triển khai thử nghiệm có chứa thuật toán tạo số "có thể đảo ngược". Điều này cho phép bạn "tua lại" PRNG hoặc thậm chí truy vấn nó để xem N đầu ra cuối cùng là gì. Nó sẽ cho phép gỡ lỗi sâu hơn nhiều trong các tình huống nhất định.
Darien

Điều này không phải là ngu ngốc - đó là phương pháp tương tự Google sử dụng để tái tạo tiêm thất bại trong các bài kiểm tra Spanner theo bài báo của họ :)
Akshat Mahajan

6

"Có phải là ngẫu nhiên (đủ)" hóa ra là một câu hỏi cực kỳ tinh tế. Câu trả lời ngắn gọn là một bài kiểm tra đơn vị truyền thống sẽ không cắt nó - bạn sẽ cần tạo ra một loạt các giá trị ngẫu nhiên và gửi chúng cho các bài kiểm tra thống kê khác nhau mang lại cho bạn sự tin cậy cao rằng chúng đủ ngẫu nhiên cho nhu cầu của bạn.

Sẽ có một mô hình - chúng tôi đang sử dụng các trình tạo số ngẫu nhiên psuedo. Nhưng đến một lúc nào đó mọi thứ sẽ "đủ tốt" cho ứng dụng của bạn (nơi đủ tốt để thay đổi RẤT NHIỀU giữa các trò chơi nói ở một đầu, trong đó các trình tạo tương đối đơn giản đủ, tất cả các cách để mã hóa nơi bạn thực sự cần các chuỗi không thể xác định được bởi một kẻ tấn công quyết tâm và được trang bị tốt).

Bài viết Wikipedia http://en.wikipedia.org/wiki/Randomness_tests và các liên kết tiếp theo của nó có nhiều thông tin hơn.


Ngay cả các PRNG tầm thường cũng sẽ không hiển thị bất kỳ patters nào trong bất kỳ bài kiểm tra thống kê nào. Đối với các PRNG tốt, thực tế không thể phân biệt chúng với các số ngẫu nhiên thực.
CodeInChaos

4

Tôi có hai câu trả lời cho bạn.

=== TRẢ LỜI ĐẦU TIÊN ===

Ngay khi tôi thấy tiêu đề của câu hỏi của bạn, tôi đã nhảy vào và đề xuất giải pháp. Giải pháp của tôi giống như những gì một số người khác đã đề xuất: giả lập trình tạo số ngẫu nhiên của bạn. Rốt cuộc, tôi đã xây dựng một số chương trình khác nhau yêu cầu thủ thuật này để viết các bài kiểm tra đơn vị tốt và tôi đã bắt đầu thực hiện truy cập giả định vào các số ngẫu nhiên thành một thông lệ tiêu chuẩn trong tất cả mã hóa của mình.

Nhưng sau đó tôi đọc câu hỏi của bạn. Và đối với vấn đề cụ thể mà bạn mô tả, đó không phải là câu trả lời. Vấn đề của bạn không phải là bạn cần phải dự đoán một quy trình sử dụng các số ngẫu nhiên (vì vậy nó có thể kiểm chứng được). Thay vào đó, vấn đề của bạn là xác minh rằng thuật toán của bạn ánh xạ đầu ra ngẫu nhiên thống nhất từ ​​RNG của bạn sang đầu ra đồng nhất trong các ràng buộc từ thuật toán của bạn - rằng nếu RNG nằm dưới đồng nhất, nó sẽ dẫn đến thời gian kiểm tra phân bố đồng đều (theo hạn chế vấn đề).

Đó là một vấn đề thực sự khó khăn (nhưng khá rõ ràng). Điều đó có nghĩa là đó là một vấn đề QUAN TÂM. Tôi bắt đầu nghĩ ra một số ý tưởng thực sự tuyệt vời để giải quyết vấn đề này. Quay lại khi tôi còn là một lập trình viên nóng bỏng, tôi có thể đã bắt đầu làm một cái gì đó với những ý tưởng này. Nhưng tôi không còn là một lập trình viên nóng bỏng nữa ... Tôi thích rằng bây giờ tôi có nhiều kinh nghiệm hơn và có kỹ năng hơn.

Vì vậy, thay vì lao vào vấn đề khó khăn, tôi tự nghĩ: giá trị của cái này là gì? Và câu trả lời thật đáng thất vọng. Lỗi của bạn đã được giải quyết và bạn sẽ cẩn thận về vấn đề này trong tương lai. Hoàn cảnh bên ngoài không thể kích hoạt vấn đề, chỉ thay đổi thuật toán của bạn. Lý do DUY NHẤT để giải quyết vấn đề thú vị này là để đáp ứng các thực tiễn của TDD (Test Driven Design). Nếu có một điều mà tôi đã học được thì đó là việc tuân thủ một cách mù quáng bất kỳ thực hành nào khi nó không có giá trị gây ra vấn đề. Đề nghị của tôi là thế này: Chỉ cần không viết một bài kiểm tra cho điều này, và tiếp tục.


=== TRẢ LỜI THỨ HAI ===

Wow ... thật là một vấn đề tuyệt vời!

Những gì bạn cần làm ở đây là viết một bài kiểm tra xác minh rằng thuật toán của bạn để chọn ngày và giờ kiểm tra sẽ tạo ra đầu ra được phân phối đồng đều (trong các ràng buộc vấn đề) nếu RNG mà nó sử dụng tạo ra các số phân phối đồng đều. Dưới đây là một số cách tiếp cận, được sắp xếp theo mức độ khó khăn.

  1. Bạn có thể áp dụng vũ lực. Chỉ cần chạy thuật toán một bó WHOLE nhiều lần, với RNG thực sự là đầu vào. Kiểm tra kết quả đầu ra để xem nếu chúng được phân phối đồng đều. Thử nghiệm của bạn sẽ cần phải thất bại nếu phân phối thay đổi từ đồng đều hoàn toàn hơn một ngưỡng nhất định và để đảm bảo bạn bắt gặp sự cố, ngưỡng này không thể được đặt ở mức thấp. Điều đó có nghĩa là bạn sẽ cần số lần chạy RẤT NHIỀU để chắc chắn rằng xác suất dương tính giả (lỗi thử nghiệm do cơ hội ngẫu nhiên) là rất nhỏ (<1% đối với cơ sở mã cỡ trung bình, thậm chí ít hơn đối với một cơ sở mã lớn).

  2. Xem xét thuật toán của bạn như là một hàm lấy kết nối của tất cả đầu ra RNG làm đầu vào, sau đó tạo thời gian kiểm tra làm đầu ra. Nếu bạn biết rằng chức năng này là liên tục, thì có một cách để kiểm tra tài sản của bạn. Thay thế RNG bằng RNG có thể giả được và chạy thuật toán nhiều lần, tạo ra đầu ra RNG phân tán đồng đều. Vì vậy, nếu mã của bạn yêu cầu 2 cuộc gọi RNG, mỗi cuộc gọi trong phạm vi [0..1], bạn có thể có bài kiểm tra chạy thuật toán 100 lần, trả về các giá trị [(0,0,0.0), (0,0,0.1), (0,0, 0,2), ... (0,0,0,9), (0,1,0,0), (0,1,0,1), ... (0,9,0,9)]. Sau đó, bạn có thể kiểm tra xem đầu ra của 100 lần chạy có được phân phối đồng đều trong phạm vi cho phép hay không.

  3. Nếu bạn THỰC SỰ cần xác minh thuật toán một cách đáng tin cậy và bạn không thể đưa ra các giả định về thuật toán HOẶC chạy nhiều lần, thì bạn vẫn có thể tấn công vấn đề, nhưng bạn có thể cần một số ràng buộc về cách bạn lập trình thuật toán . Hãy xem PyPy và cách tiếp cận Không gian đối tượng của họ làm ví dụ. Bạn có thể tạo một Không gian đối tượng, thay vì thực sự thực hiện thuật toán, thay vào đó chỉ tính hình dạng của phân phối đầu ra (giả sử rằng đầu vào RNG là đồng nhất). Tất nhiên, điều này đòi hỏi bạn phải xây dựng một công cụ như vậy và thuật toán của bạn được xây dựng trong PyPy hoặc một số công cụ khác, nơi dễ dàng thực hiện các sửa đổi mạnh mẽ cho trình biên dịch và sử dụng nó để phân tích mã.


3

Đối với các bài kiểm tra đơn vị, thay thế trình tạo ngẫu nhiên bằng một lớp tạo ra kết quả có thể dự đoán được bao gồm tất cả các trường hợp góc . Tức là đảm bảo giả ngẫu nhiên của bạn tạo ra giá trị thấp nhất có thể và giá trị cao nhất có thể, và kết quả tương tự nhiều lần liên tiếp.

Bạn không muốn các bài kiểm tra đơn vị của mình bỏ qua, ví dụ như các lỗi do lỗi xảy ra khi Random.nextInt (1000) trả về 0 hoặc 999.


3

Bạn có thể muốn xem Sevcikova et al: "Kiểm tra tự động các hệ thống ngẫu nhiên: Phương pháp tiếp cận có căn cứ thống kê" ( PDF ).

Phương pháp này được triển khai trong các trường hợp thử nghiệm khác nhau cho nền tảng mô phỏng UrbanSim .


Đó là thứ tốt, ở đó.
KeithS

2

Một cách tiếp cận biểu đồ đơn giản là bước đầu tiên tốt, nhưng không đủ để chứng minh tính ngẫu nhiên. Đối với PRNG thống nhất, bạn cũng sẽ (ít nhất) tạo ra một biểu đồ phân tán 2 chiều (trong đó x là giá trị trước đó và y là giá trị mới). Âm mưu này cũng nên được thống nhất. Điều này là phức tạp trong tình huống của bạn bởi vì có sự phi tuyến tính có chủ ý trong hệ thống.

Cách tiếp cận của tôi sẽ là:

  1. xác nhận (hoặc lấy như đã cho) rằng PRNG nguồn là đủ ngẫu nhiên (sử dụng các biện pháp thống kê tiêu chuẩn)
  2. xác minh rằng chuyển đổi PRNG-datetime không giới hạn là đủ ngẫu nhiên trên không gian đầu ra (điều này xác minh sự thiếu sai lệch trong chuyển đổi). Kiểm tra tính đồng nhất đơn hàng đầu tiên của bạn nên có đủ ở đây.
  3. xác minh rằng các trường hợp bị ràng buộc là đủ thống nhất (một thử nghiệm tính đồng nhất đơn hàng đầu tiên trên các thùng hợp lệ).

Mỗi thử nghiệm này được thống kê và yêu cầu một số lượng lớn các điểm mẫu để tránh dương tính giả và âm tính giả với độ tin cậy cao.

Theo bản chất của thuật toán chuyển đổi / ràng buộc:

Cho trước: phương thức tạo giá trị giả ngẫu nhiên p trong đó 0 <= p <= M

Cần: đầu ra y trong (có thể không liên tục) phạm vi 0 <= y <= N <= M

Thuật toán:

  1. tính toán r = floor(M / N), đó là số lượng phạm vi đầu ra hoàn chỉnh phù hợp trong phạm vi đầu vào.
  2. tính giá trị tối đa chấp nhận được cho p :p_max = r * N
  3. tạo giá trị cho p cho đến khi p_maxtìm thấy giá trị nhỏ hơn hoặc bằng
  4. tính toán y = p / r
  5. nếu y chấp nhận được, hãy trả lại, nếu không lặp lại với bước 3

Điều quan trọng là loại bỏ các giá trị không được chấp nhận thay vì gấp không đồng đều.

trong mã giả:

# 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

Ngoài việc xác thực rằng mã của bạn không thất bại hoặc ném ngoại lệ đúng vào đúng nơi bạn có thể tạo cặp đầu vào / phản hồi hợp lệ (thậm chí tính toán thủ công), hãy cung cấp đầu vào trong thử nghiệm và đảm bảo rằng nó trả về phản hồi mong đợi. Không tuyệt vời, nhưng đó là tất cả những gì bạn có thể làm, imho. Tuy nhiên, trong trường hợp của bạn, điều đó không thực sự ngẫu nhiên, một khi bạn tạo lịch trình của mình, bạn có thể kiểm tra sự phù hợp với quy tắc - phải có 3 lần kiểm tra mỗi tuần, từ 9-9; không có nhu cầu thực sự hoặc khả năng kiểm tra thời gian chính xác khi kiểm tra xảy ra.


1

Thực sự không có cách nào tốt hơn là chạy nó một loạt lần và xem nếu bạn có được bản phân phối mà bạn muốn. Nếu bạn có 50 lịch kiểm tra tiềm năng được phép, bạn sẽ chạy thử 500 lần và đảm bảo mỗi lịch được sử dụng gần 10 lần. Bạn có thể kiểm soát các hạt tạo ngẫu nhiên của mình để làm cho nó có tính quyết định hơn, nhưng điều đó cũng sẽ làm cho các thử nghiệm của bạn được kết hợp chặt chẽ hơn với các chi tiết thực hiện.


Nhưng nếu nó thực sự ngẫu nhiên, thì đôi khi, một số lịch trình sẽ không được sử dụng; và đôi khi, một số lịch trình sẽ được sử dụng hơn 20 lần. Tôi không biết bạn dự định kiểm tra xem mỗi lịch trình được sử dụng "gần 10 lần" như thế nào, nhưng bất kỳ điều kiện nào bạn kiểm tra ở đây, bạn sẽ có một bài kiểm tra đôi khi thất bại khi chương trình hoạt động.
Dawood nói phục hồi Monica

@DawoodibnKareem với cỡ mẫu đủ (và giới hạn hợp lý về tính đồng nhất), bạn có thể giảm khả năng thử nghiệm thất bại xuống còn 1 phần tỷ. Và nói chung, số liệu thống kê như thế là theo cấp số nhân với n nên sẽ mất ít hơn bạn mong đợi để có được những con số đó.
mbrig

1

Không thể kiểm tra một điều kiện mơ hồ không có định nghĩa cụ thể. Nếu ngày được tạo vượt qua tất cả các bài kiểm tra thì về mặt lý thuyết, ứng dụng của bạn hoạt động chính xác. Máy tính không thể cho bạn biết nếu ngày là "đủ ngẫu nhiên" vì nó không thể thừa nhận các tiêu chí cho một bài kiểm tra như vậy. Nếu tất cả các bài kiểm tra vượt qua nhưng hành vi của ứng dụng vẫn không phù hợp thì phạm vi kiểm tra của bạn không đầy đủ về mặt thực nghiệm (từ góc độ TDD).

Theo quan điểm của tôi, tốt nhất của bạn là thực hiện một số hạn chế tạo ngày tùy ý để phân phối vượt qua kiểm tra mùi của con người.


2
Bạn hoàn toàn có thể xác định tính ngẫu nhiên thông qua thử nghiệm tự động. Bạn chỉ cần tạo ra một số lượng mẫu đủ lớn và áp dụng các thử nghiệm ngẫu nhiên tiêu chuẩn để phát hiện các sai lệch trong hệ thống. Đây là một bài tập lập trình undergrad khá chuẩn.
Frank Szczerba

0

Chỉ cần ghi lại đầu ra của bộ ngẫu nhiên của bạn (cho dù giả hoặc lượng tử / hỗn loạn hoặc thế giới thực). Sau đó lưu và phát lại các chuỗi "ngẫu nhiên" phù hợp với yêu cầu kiểm tra của bạn hoặc để lộ các vấn đề và lỗi tiềm ẩn khi bạn xây dựng các trường hợp kiểm tra đơn vị của mình.


0

Trường hợp này có vẻ lý tưởng cho thử nghiệm dựa trên tài sản .

Tóm lại, đây là một chế độ thử nghiệm trong đó khung kiểm tra tạo đầu vào cho mã theo xác nhận kiểm tra và kiểm tra xác thực các thuộc tính của đầu ra. Khung sau đó có thể đủ thông minh để "tấn công" mã đang thử nghiệm và cố gắng khắc phục lỗi đó. Khung thường cũng đủ thông minh để chiếm quyền điều khiển trình tạo số ngẫu nhiên của bạn. Thông thường, bạn có thể định cấu hình khung để tạo tối đa N trường hợp thử nghiệm hoặc chạy tối đa N giây và nhớ các trường hợp thử nghiệm thất bại từ lần chạy trước và chạy lại chúng với phiên bản mã mới hơn trước. Điều này cho phép chu kỳ lặp nhanh trong quá trình phát triển và thử nghiệm chậm, toàn diện ngoài băng / trong CI.

Đây là một ví dụ (ngu ngốc, thất bại) kiểm tra sumchức năng:

@given(lists(floats()))
def test_sum(alist):
    result = sum(alist)
    assert isinstance(result, float)
    assert result > 0
  • khung kiểm tra sẽ tạo ra một danh sách tại một thời điểm
  • nội dung của danh sách sẽ là số dấu phẩy động
  • sum được gọi và các thuộc tính của kết quả được xác nhận
  • kết quả luôn luôn nổi
  • kết quả là tích cực

Bài kiểm tra này sẽ tìm thấy một loạt các "lỗi" trong sum(nhận xét nếu bạn có thể tự đoán tất cả những lỗi này):

  • sum([]) is 0 (int, không phải là float)
  • sum([-0.9]) là tiêu cực
  • sum([0.0]) không hoàn toàn tích cực
  • sum([..., nan]) is nan không tích cực

Với cài đặt mặc định, hpythesishủy bỏ kiểm tra sau khi tìm thấy 1 đầu vào "xấu", điều này tốt cho TDD. Tôi nghĩ rằng có thể định cấu hình nó để báo cáo nhiều / tất cả các đầu vào "xấu", nhưng tôi không thể tìm thấy các tùy chọn đó ngay bây giờ.

Trong trường hợp OP, các thuộc tính được xác thực sẽ phức tạp hơn: loại kiểm tra A hiện tại, loại kiểm tra A ba lần một tuần, thời gian kiểm tra B luôn luôn vào lúc 12 giờ tối, loại kiểm tra C từ 9 đến 9, [lịch trình nhất định là trong một tuần] kiểm tra các loại A, B, C đều có mặt, v.v.

Thư viện nổi tiếng nhất là QuickCheck for Haskell, xem trang Wikipedia bên dưới để biết danh sách các thư viện đó bằng các ngôn ngữ khác:

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

Giả thuyết (đối với Python) có một bài viết hay về loại thử nghiệm này:

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


-1

những gì bạn có thể kiểm tra đơn vị là logic xác định xem ngày ngẫu nhiên là hợp lệ hay nếu một ngày ngẫu nhiên khác cần phải được chọn.

Không có cách nào để kiểm tra một trình tạo ngày ngẫu nhiên mà không nhận được một loạt các ngày và quyết định xem chúng có ngẫu nhiên không.


-1

Mục tiêu của bạn không phải là viết bài kiểm tra đơn vị và vượt qua chúng, mà là để đảm bảo rằng chương trình của bạn phù hợp với yêu cầu của nó. Cách duy nhất bạn có thể làm điều này, là xác định chính xác yêu cầu của bạn ở nơi đầu tiên. Ví dụ: bạn đã đề cập đến "ba lần kiểm tra hàng tuần vào các thời điểm ngẫu nhiên". Tôi muốn nói các yêu cầu là: (a) 3 lần kiểm tra (không phải 2 hoặc 4), (b) vào những thời điểm không thể dự đoán được bởi những người không muốn bị kiểm tra bất ngờ và (c) không quá gần nhau - hai cuộc kiểm tra cách nhau năm phút có lẽ là vô nghĩa, cũng có thể không quá xa nhau.

Vì vậy, bạn viết ra các yêu cầu chính xác hơn tôi đã làm. (a) và (c) là dễ dàng. Đối với (b), bạn có thể viết một số mã thông minh nhất có thể để cố gắng dự đoán lần kiểm tra tiếp theo và để vượt qua bài kiểm tra đơn vị, mã đó không thể dự đoán tốt hơn dự đoán thuần túy.

Và tất nhiên bạn cần lưu ý rằng nếu việc kiểm tra của bạn thực sự ngẫu nhiên, bất kỳ thuật toán dự đoán nào cũng có thể chính xác, vì vậy bạn phải chắc chắn rằng bạn và các đơn vị kiểm tra của bạn không hoảng sợ nếu điều đó xảy ra. Có thể thực hiện một vài bài kiểm tra nữa. Tôi sẽ không kiểm tra trình tạo số ngẫu nhiên, vì cuối cùng thì đó là lịch kiểm tra, và việc nó được tạo ra như thế nào không quan trọng.


Không, nhất quyết không. Các bài kiểm tra đơn vị chứng minh chương trình phù hợp với yêu cầu của nó, vì vậy hai là một và giống nhau. Và tôi không kinh doanh trong việc viết phần mềm dự đoán cho các thuật toán ngẫu nhiên đảo ngược. Nếu tôi là tôi sẽ không nói với bạn về điều đó, tôi sẽ giết chết các trang web an toàn bằng cách dự đoán chìa khóa của họ và bán bí mật cho người trả giá cao nhất. Doanh nghiệp của tôi đang viết một lịch trình tạo ra thời gian có thể hạn chế nhưng không thể đoán trước được trong các ràng buộc và tôi cần các thử nghiệm xác định để chứng minh rằng tôi đã làm như vậy, không phải là xác suất mà tôi chắc chắn.
KeithS
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.