72

我不认为这是特定于语言或框架的,但我使用的是 xUnit.net 和 C#。

我有一个函数可以返回一定范围内的随机日期。我传入一个日期,返回日期总是在给定日期之前 1 到 40 年的范围内。

现在我只是想知道是否有一个好的方法来对此进行单元测试。最好的方法似乎是创建一个循环并让函数运行 100 次,并断言这 100 个结果中的每一个都在所需的范围内,这是我目前的方法。

我也意识到,除非我能够控制我的随机生成器,否则不会有完美的解决方案(毕竟,结果是随机的),但我想知道当你必须测试返回随机结果的功能时你会采取什么方法一定范围?

4

11 回答 11

58

模拟或伪造随机数生成器

做这样的事情......我没有编译它,所以可能会有一些语法错误。

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 以返回罐头。

于 2008-11-22T21:54:58.467 回答
35

除了测试该函数是否返回所需范围内的日期外,您还希望确保结果分布良好。您描述的测试将通过一个简单地返回您发送日期的函数!

因此,除了多次调用该函数并测试结果是否保持在所需范围内之外,我还会尝试评估分布,也许通过将结果放入存储桶并检查存储桶是否具有大致相等数量的结果。完毕。您可能需要超过 100 次调用才能获得稳定的结果,但这听起来不像是一个昂贵的(运行时明智的)函数,因此您可以轻松地运行它几 K 次迭代。

我之前遇到过不统一的“随机”函数的问题。它们可能真的很痛苦,值得尽早测试。

于 2008-11-22T21:48:01.647 回答
11

我认为您要测试这个问题的三个不同方面。

第一个:我的算法是正确的吗?也就是说,给定一个正常运行的随机数生成器,它会生成随机分布在该范围内的日期吗?

第二个:算法是否正确处理边缘情况?也就是说,当随机数生成器产生最高或最低允许值时,是否有任何中断?

第三个:我的算法实现是否有效?也就是说,给定一个已知的伪随机输入列表,它是否会产生预期的伪随机日期列表?

前两件事不是我要构建到单元测试套件中的东西。在设计系统时,我会证明它们。正如 daniel.rikowski 建议的那样,我可能会通过编写一个生成无数日期并执行卡方检验的测试工具来做到这一点。我还要确保这个测试工具在它处理两种边缘情况之前不会终止(假设我的随机数范围足够小,我可以摆脱这个)。我会记录下来,以便任何前来尝试改进算法的人都知道这是一个突破性的变化。

最后一个我要进行单元测试的东西。我需要知道代码中没有任何东西会破坏该算法的实现。发生这种情况时,我得到的第一个迹象是测试将失败。然后我会回到代码并发现其他人认为他们正在修复某些东西并破坏了它。如果有人确实修复了算法,那么他们也必须修复这个测试。

于 2008-11-23T01:41:54.420 回答
8

您无需控制系统即可使结果具有确定性。您采用了正确的方法:确定函数输出的重要内容并对其进行测试。在这种情况下,重要的是结果在 40 天的范围内,并且您正在对此进行测试。同样重要的是它并不总是返回相同的结果,因此也要对此进行测试。如果你想变得更漂亮,你可以测试结果是否通过了某种随机性测试。

于 2008-11-22T21:45:21.043 回答
5

通常我完全使用您建议的方法:控制随机生成器。使用默认种子初始化它以进行测试(或用适合我的测试用例的代理返回数字替换他),因此我具有确定性/可测试的行为。

于 2008-11-22T21:43:18.520 回答
4

如果您想检查随机数的质量(在独立性方面),有几种方法可以做到这一点。一种好方法是卡方检验

于 2008-11-22T21:52:33.633 回答
3

当然,使用固定的种子随机数生成器可以正常工作,但即便如此,您也只是在尝试测试您无法预测的内容。没关系。这相当于有一堆固定的测试。但是,请记住——测试什么是重要的,但不要尝试测试所有内容。我相信随机测试是一种尝试测试所有内容的方法,它效率不高(或速度不快)。在遇到错误之前,您可能必须运行大量随机测试。

我想在这里说明的是,您应该简单地为您在系统中发现的每个错误编写一个测试。您测试边缘情况以确保您的功能即使在极端条件下也能运行,但实际上这是您可以做的最好的事情,而不需要花费太多时间或使单元测试运行缓慢,或者只是浪费处理器周期。

于 2010-01-29T17:51:23.917 回答
2

根据您的函数创建随机日期的方式,您可能还需要检查非法日期:不可能的闰年,或 30 天月份的第 31 天。

于 2008-11-23T01:50:30.953 回答
2

没有表现出确定性行为的方法无法正确进行单元测试,因为结果会因执行而异。解决此问题的一种方法是为随机数生成器植入单元测试的固定值。您还可以提取日期生成类的随机性(从而应用单一责任原则),并为单元测试注入已知值。

于 2009-11-11T12:37:53.637 回答
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。

测试随机函数的真实随机性完全是一个单独的测试。我会避免在单元测试中测试随机性,而是会进行单独的测试,并在您使用的编程语言中搜索随机函数的真正随机性。非确定性测试(如果有的话)应该被排除在单元测试之外。也许有一个单独的套件用于这些测试,这需要人工输入或更长的运行时间,以最大限度地减少失败的可能性,而这实际上是通过。

于 2010-02-02T01:01:10.137 回答
0

我不认为单元测试是为此而生的。您可以对返回随机值但使用固定种子的函数使用单元测试,在这种情况下它们不是随机的,可以这么说,对于随机种子,我认为单元测试不是您想要的,例如对于 RNG,您的意思是进行系统测试,在其中您多次运行 RNG 并查看它的分布或时刻。

于 2011-05-11T14:16:00.640 回答