【问题标题】:Unit testing - how do I test a function that returns random output?单元测试 - 如何测试返回随机输出的函数?
【发布时间】:2011-02-06 18:20:27
【问题描述】:

我有一个函数,它接受两个参数,并在 50% 的时间内返回一个或另一个。

对此的单元测试应该确定两个参数都可以返回。幸运的是,我不需要证明每个参数的概率都是 50%,但我确实需要证明两个参数都可以返回。

如何为这个函数编写测试用例?

【问题讨论】:

  • 不是单元测试的全部内容,您可以验证输出。在您的情况下,输出是随机的,因此没有单元测试?
  • @adamse:您必须使测试适应单元测试软件的随机性。这意味着函数的任何一次调用都不一定是错误的(尽管可能有一些值范围是可以的,而其他值不是,应该验证),但是有聚合属性。如果在 10 次以上的运行中,您看到 1 个案例返回第一个值,而 9 个案例返回第二个值,您可能会怀疑 50% 的比率。你不能确定结果是假的,但它们不太可能是正确的。经常重复,你可以说“是的,它的行为正确”。
  • @jonathan:感谢您的解释!我对单元测试不是很熟悉。

标签: unit-testing


【解决方案1】:

你通过分离和/或injection来测试它:

// ** WARNING: Untested code

原创

void funcToTest(String a, String b) {
  int i = new Random().nextInt(2);
  return ((i == 0) ? a : b);
}

分离

void funcToTest(String a, String b) {
  return funcToTest(a, b, (new Random().nextInt(2) == 0));
}
void funcToTest(String a, String b, boolean r) {
  return (r ? a : b);
}

现在测试

funcToTest(String a, String b, boolean r).

您相信这一点,如果您测试上述内容,那么

funcToTest(String a, String b)

会正常工作。

注入

interface BoolRandomizer {
  bool getBool();
}
BoolRandomizer BOOL_RANDOMIZER = new BoolRandomizer() {
  public bool getBool() { return (new Random().nextInt(2) == 0); }
}
BoolRandomizer ALWAYS_FALSE_RANDOMIZER = new BoolRandomizer() {
  public bool getBool() { return false; }
}
BoolRandomizer ALWAYS_TRUE_RANDOMIZER = new BoolRandomizer() {
  public bool getBool() { return true; }
}

class ClassUnderTest {
    private final BoolRandomizer br;
    ClassUnderTest() { this(BOOL_RANDOMIZER); }
    ClassUnderTest(BoolRandomizer br) { this.br = br; }
    void funcToTest(String a, String b) {
      return (br.getBool() ? a : b);
    }
}

现在测试

new ClassUnderTest(ALWAYS_FALSE_RANDOMIZER).funcToTest()

new ClassUnderTest(ALWAYS_TRUE_RANDOMIZER).funcToTest()

当然有更复杂/更巧妙的方法可以做到这一点,但这是基本思想。我将这些技术用于一副纸牌程序来测试洗牌功能。另一种情况:通过调用系统 API gettime() 使用当前时间的函数可能很难测试,除非您愿意在一天中非常特定的时间运行单元测试:-)。分离和注入易于测试。

【讨论】:

    【解决方案2】:

    您可以做以下两件事之一:

    1. 制作一个可以在单元测试时返回一致数字的存根 random() 生成器。
    2. 运行您的代码 N 次,其中 N 足够高,只要代码正常运行,您就可以获得一致的结果。

    选项#1 几乎总是首选。通过包含随机数生成器,您不再测试原子代码单元,并且通过在单元测试中包含随机组件,您需要进行统计以验证代码是否正常工作。在合理数量的测试中,选项#2 永远无法发现一些错误,这令人担忧。

    【讨论】:

      【解决方案3】:

      单元测试结果不是给定响应的单个值,它是一系列输入值的分布。概率意味着您必须运行许多测试来构建它。

      如果您的对象真的应该在平均一半的时间内返回一个值,而在其余时间返回另一个值,那么适当的测试将是运行许多测试并确保限制行为为您提供所需的分布。建立适当的结果分布并确保它符合您的期望。

      【讨论】:

      • 嗯,她确实说过:“幸运的是,我不需要证明每个人的概率都是 50% ......”
      • 我不确定我是否同意这种说法。
      【解决方案4】:

      如果决定是真正随机的(或伪随机的),那么您可以从您的函数中分离出从另一个结果中选择一个结果的问题。例如,您的函数可以接受一个委托或另一个对象作为参数,其唯一工作是“掷硬币”,但不关心这两个参数或关于它们的任何逻辑。然后,您的单元测试可以提供一个模拟决策者对象,该对象返回测试想要检查的任何结果。

      【讨论】:

        【解决方案5】:

        不确定这是否是最好的方法,但我会使用带有两个标志的while 循环,这些标志是我根据从函数中获得的值设置的。我还会使用某种限制,这样如果测试失败(即没有收到两个值之一),测试就不会进入无限循环。所以:

        int limit = 100;
        int i = 0;
        while(!firstValueReceived && !secondValueReceived && i < limit) {
           int value = randomFunc();
        
           if(!firstValueReceived) {
              firstValueReceived = (value == firstValue);
           }
        
           if(!secondValueReceived) {
              secondValueReceived = (value == secondValue);
           }
           i++;
        }
        
        assertTrue(firstValueReceived && secondValueReceived);
        

        我想你可以使用测试挂起作为衡量它是否失败的指标,这样你就可以取消限制:

        while(!firstValueReceived && !secondValueReceived) {
           int value = randomFunc();
        
           if(!firstValueReceived) {
              firstValueReceived = (value == firstValue);
           }
        
           if(!secondValueReceived) {
              secondValueReceived = (value == secondValue);
           }
        }
        
        assertTrue(firstValueReceived && secondValueReceived);
        

        【讨论】:

          【解决方案6】:

          如果随机性基于它调用的随机数生成器,您可以安装一个存根 random() 函数,该函数返回各种结果以提供您预期的输出。

          【讨论】:

          • 这个。将随机数生成器传递到需要它的对象或方法中,您就可以保证它的行为。
          • 不错!根本没有考虑模拟 random() 方法。
          • 您还可以使用模拟随机数来测试极端情况,例如您的 random() 返回 0.0 或 1.0。
          猜你喜欢
          • 2010-09-23
          • 1970-01-01
          • 1970-01-01
          • 2014-02-08
          • 1970-01-01
          • 2018-03-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多