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