필자의 현재 프로젝트는 간결하게 "무제한 무작위 이벤트"의 생성과 관련이있다. 기본적으로 검사 일정을 생성 중입니다. 그들 중 일부는 엄격한 일정 제약 조건을 기반으로합니다. 금요일 오전 10시에 일주일에 한 번 검사를 수행합니다. 다른 검사는 "무작위"입니다. "1 주일에 3 번씩 검사해야 함", "9 AM-9PM 시간 사이에 검사해야 함"및 "동일한 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 거부 (범위 내에서 숫자를 비트 시프트)는 설명하기 쉬운 이유로 끔찍했습니다. 최대 값보다 작을 때까지 숫자를 2로 나누면 최대한 빨리 종료되고 사소한 범위가 아닌 경우 결과를 상위 3 분의 1로 편향시킵니다 (히스토그램의 자세한 결과에서 볼 수 있음) ). 이것은 내가 완성한 날짜부터 본 행동입니다. 모든 시간은 매우 구체적인 날 오후에있었습니다.
MSB 거부 (범위 내에있을 때까지 한 번에 하나씩 가장 중요한 비트를 제거)가 더 좋지만, 각 비트마다 매우 큰 숫자를 잘라내므로 고르게 분산되지 않습니다. 당신은 상단과 하단에 숫자를 얻을 가능성이 없으므로 중간 1/3을 향한 치우침을 얻습니다. 이는 임의의 데이터를 종 모양의 곡선으로 "정규화"하려는 사람에게 도움이 될 수 있지만 2 개 이상의 더 작은 임의의 숫자 (주사위 던지기와 비슷한)의 합은 더 자연스러운 곡선을 제공합니다. 내 목적으로는 실패합니다.
이 테스트를 통과 한 유일한 방법은 모듈로 나누기에 의해 제약을받는 것이 었으며, 이는 또한 세 가지 중에서 가장 빠른 것으로 밝혀졌습니다. Modulo는 정의에 따라 사용 가능한 입력을 고려하여 가능한 한 분포를 생성합니다.