我不认为这是特定于语言或框架,但我使用的是xUnit.net和C#。
我有一个函数可以返回某个范围内的随机日期。我传递了一个日期,返回日期总是在给定日期之前1到40年的范围内。
现在我只是想知道是否有一种好的方法来对此进行单元测试。最好的方法似乎是创建一个循环并让函数运行100次并断言这100个结果中的每一个都在所需的范围内,这是我目前的方法。
我也意识到,除非我能够控制我的Random发生器,否则将没有一个完美的解决方案(毕竟,结果是随机的),但我想知道当你必须测试返回a的功能时你采取了什么方法随机结果在一定范围内?
答案 0 :(得分:51)
模拟或伪造随机数生成器
执行类似的操作......我没有编译它,因此可能存在一些语法错误。
public interface IRandomGenerator
{
double Generate(double max);
}
public class SomethingThatUsesRandom
{
private readonly IRandomGenerator _generator;
private class DefaultRandom : IRandomGenerator
{
public double Generate(double max)
{
return (new Random()).Next(max);
}
}
public SomethingThatUsesRandom(IRandomGenerator generator)
{
_generator = generator;
}
public SomethingThatUsesRandom() : this(new DefaultRandom())
{}
public double MethodThatUsesRandom()
{
return _generator.Generate(40.0);
}
}
在你的测试中,只需假冒或嘲笑IRandomGenerator以返回罐装的东西。
答案 1 :(得分:32)
除了测试函数返回所需范围内的日期之外,还要确保结果分布均匀。您描述的测试将传递一个函数,该函数只返回您发送的日期!
因此,除了多次调用函数并测试结果保持在所需范围外,我还会尝试评估分布,可能是将结果放入存储桶并检查存储桶的结果数量大致相等你完成后。您可能需要超过100次调用才能获得稳定的结果,但这听起来并不像一个昂贵的(运行时明智的)函数,因此您可以轻松地运行它几次K迭代。
我之前遇到过非均匀“随机”功能的问题......它们可能是一个真正的痛苦,值得早期测试。
答案 2 :(得分:9)
我认为您测试此问题有三个不同方面。
第一个:我的算法是正确的吗?也就是说,如果一个功能正常的随机数生成器,它会产生在整个范围内随机分布的日期吗?
第二个:算法是否正确处理边缘情况?也就是说,当随机数发生器产生最高或最低允许值时,是否有任何破坏?
第三个:我的算法实现是否有效?也就是说,给定一个已知的伪随机输入列表,它是否产生了预期的伪随机日期列表?
前两个东西不是我构建到单元测试套件中的东西。它们是我在设计系统时证明的东西。我可能会通过写一个生成无数日期并进行卡方检验的测试工具来做到这一点,正如daniel.rikowski建议的那样。我还要确保这个测试工具没有终止,直到它处理了两个边缘情况(假设我的随机数范围足够小,我可以逃脱这个)。我会记录这一点,以便任何人出现并尝试改进算法都会知道这是一个重大改变。
最后一个是我要进行单元测试的东西。我需要知道,没有任何内容侵入到破坏其算法实现的代码中。当发生这种情况时我会得到的第一个标志是测试失败。然后我会回到代码中,发现其他人认为他们正在修理某些东西并改为破坏它。如果某人 修复了算法,他们也会修复此测试。
答案 3 :(得分:8)
您无需控制系统即可使结果具有确定性。您采用的是正确的方法:确定函数输出的重要性并测试。在这种情况下,重要的是结果在40天的范围内,并且您正在测试它。同样重要的是它并不总是返回相同的结果,所以也要测试它。如果你想成为更好的,你可以测试结果通过某种随机性测试..
答案 4 :(得分:5)
Normaly我完全使用您建议的方法:控制随机生成器。 使用默认种子初始化它以进行测试(或者通过代理返回适合我的测试用例的数字替换它),因此我有确定性/可测试的行为。
答案 5 :(得分:4)
如果要检查随机数的质量(就独立性而言),有几种方法可以做到这一点。一个好方法是Chi square test。
答案 6 :(得分:2)
根据您的功能创建随机日期的方式,您可能还需要检查非法日期:不可能的闰年,或30天的第31天。
答案 7 :(得分:2)
未表现出确定性行为的方法无法进行适当的单元测试,因为结果会因执行而异。解决此问题的一种方法是seed随机数生成器,其具有用于单元测试的固定值。您还可以提取日期生成类的随机性(从而应用Single Responsibility Principle),并为单元测试注入已知值。
答案 8 :(得分:2)
当然,使用固定的种子随机数生成器可以正常工作,但即便如此,您只是试图测试您无法预测的那个。哪个好。这相当于拥有一堆固定的测试。但是,记住 - 测试什么是重要的,但不要试图测试一切。我相信随机测试是一种尝试测试一切的方法,而且效率不高(或快速)。在遇到错误之前,您可能需要运行大量的随机测试。
我想在这里得到的是,您应该只为您在系统中找到的每个错误编写一个测试。您可以测试边缘情况,以确保即使在极端条件下您的功能也在运行,但实际上这是您可以做的最好的事情,无需花费太多时间或使单元测试运行缓慢,或者只是浪费处理器周期。
答案 9 :(得分:1)
我建议覆盖随机函数。我在PHP中进行单元测试,所以我写了这段代码:
// If we are unit testing, then...
if (defined('UNIT_TESTING') && UNIT_TESTING)
{
// ...make our my_rand() function deterministic to aid testing.
function my_rand($min, $max)
{
return $GLOBALS['random_table'][$min][$max];
}
}
else
{
// ...else make our my_rand() function truly random.
function my_rand($min = 0, $max = PHP_INT_MAX)
{
if ($max === PHP_INT_MAX)
{
$max = getrandmax();
}
return rand($min, $max);
}
}
然后我按照我的要求设置了random_table。
测试随机函数的真实随机性是一个单独的测试。我会避免在单元测试中测试随机性,而是进行单独的测试,并在您正在使用的编程语言中谷歌随机函数的真正随机性。非确定性测试(如果有的话)应该被排除在单元测试之外。也许有一个单独的套件用于那些测试,需要人工输入或更长的运行时间,以最大限度地减少失败的可能性,这真的是一个过程。
答案 10 :(得分:0)
我不认为单元测试是为了这个。您可以对返回随机值的函数使用单元测试,但使用固定种子,在这种情况下,它们不是随机的,可以这么说, 对于随机种子,我不认为单元测试是你想要的,例如对于RNG来说你的意思是系统测试,你可以多次运行RNG并查看它的分布或时刻。