【问题标题】:Testing with random inputs best practices使用随机输入进行测试的最佳实践
【发布时间】:2010-09-20 08:12:19
【问题描述】:

注意:我提到接下来的几段作为背景。如果您只需要 TL;DR,请随意跳至编号问题,因为它们仅与此信息间接相关。

我目前正在编写一个 python 脚本,它使用 POSIX 日期(除其他外)做一些事情。不过,对这些进行单元测试似乎有点困难,因为可以遇到的日期和时间范围很广。

当然,对我来说,尝试测试每个可能的日期/时间组合是不切实际的,所以我想我将尝试一个单元测试来随机化输入,然后在测试失败时报告输入是什么。从统计上讲,我认为如果我尝试考虑所有潜在的问题区域(由于遗漏的东西)或测试所有案例(由于完全不可行),我可以实现更多的测试完整性,假设我运行它足够的次数。

所以这里有几个问题(主要与上述间接相关):

  1. 哪些类型的代码适合随机测试?哪些类型的代码不是?
    • 如何确定使用随机输入运行代码的次数?我问这个是因为我希望有足够大的样本来确定任何错误,但不想等待一周才能得到结果。
    • 这些类型的测试是否非常适合单元测试,或者是否有其他类型的测试可以很好地配合?
    • 还有其他最佳做法可以做这种事情吗?

相关主题:

【问题讨论】:

    标签: unit-testing language-agnostic


    【解决方案1】:

    随机测试具有巨大的优势,即可以以极低的成本生成单独的测试。即使您只有部分预言机也是如此(例如,软件会崩溃吗?)

    在复杂的系统中,随机测试会发现其他任何方法都难以发现的错误。想一想这对安全测试意味着什么:即使您不进行随机测试,黑帽也会发现您遗漏的错误。

    随机测试的一个引人入胜的子领域是随机差分测试,其中两个或多个本应表现出相同行为的系统使用共同的输入进行刺激。如果他们的行为不同,则发现了一个错误(一个或两个)。这对编译器的测试产生了很大的影响,并且总是在任何编译器中发现以前没有遇到过该技术的错误。即使您只有一个编译器,您也可以在不同的优化设置上尝试它以寻找不同的结果,当然崩溃总是意味着错误。

    【讨论】:

      【解决方案2】:

      这是我对类似问题的回答:Is it a bad practice to randomly-generate test data?。其他答案也可能有用。

      随机测试是一种不好的做法 只要你没有解决方案 oracle 问题,即 确定哪个是预期的 鉴于您的软件的结果 输入。

      如果你解决了预言机问题,你 可以比简单更进一步 随机输入生成。你可以 选择输入分布,使得 软件的特定部分 比简单的锻炼更多 随机的。

      然后您从随机测试切换到 统计检验。

      if (a > 0)
          // Do Foo
      else (if b < 0)
          // Do Bar
      else
          // Do Foobar
      

      如果您在中随机选择a 和b int 范围,你锻炼了Foo 50% 时间,Bar 25% 的时间和 Foobar 25% 的时间。这有可能 你会在Foo 中发现更多错误 比在Bar 或Foobar 中。

      如果您选择a,则它是 负 66.66% 的时间,Bar 和 Foobar 锻炼得更多 你的第一个发行版。确实 三个分支各得到锻炼 33.33% 的时间。

      当然,如果您观察到的结果是 与您预期的结果不同, 你必须记录一切可能 对重现错误很有用。

      【讨论】:

        【解决方案3】:

        这可能有点离题,但如果您使用的是 .net,则有 Pex,它执行类似于随机测试的操作,但通过尝试生成一个“随机”测试用例来进行练习,从而更加直观通过您的代码的所有路径。

        【讨论】:

          【解决方案4】:

          要使测试具有可重复性,只需使用固定的种子起始值即可。这确保了无论何时运行测试都使用相同的数据。测试将可靠地通过或失败。

          • 好/坏候选人?随机测试擅长发现边缘情况(例外)。问题是如何定义随机输入的正确结果。
          • 确定运行代码的次数:只需尝试一下,如果花费的时间太长,请减少迭代次数。您可能需要使用代码覆盖率工具来了解您的应用程序的哪些部分实际经过测试。
          • 这些类型的测试是否非常适合单元测试?是的。

          【讨论】:

            【解决方案5】:

            一些事情:

            • 通过随机测试,您无法真正判断一段代码的好,但可以判断它的坏。
            • 随机测试更适合具有随机输入的事物——一个典型的例子是任何暴露给用户的事物。因此,例如,在您的应用(或操作系统)中随机点击和输入的内容是对总体稳健性的良好测试。
            • 同样,开发人员也算作用户。因此,从您的框架中随机组装 GUI 的东西是另一个不错的选择。
            • 同样,您不会以这种方式找到所有的错误——您要寻找的是“如果我做了一百万件古怪的事情,它们中的任何一个都会导致系统损坏吗?”如果没有,您可以在一定程度上确信您的应用/操作系统/SDK/任何东西可能会在几天内向用户展示。
            • ...但是,更重要的是,如果您的 random-beater-upper 测试应用程序可以在大约 5 分钟内使您的应用程序/OS/SDK 崩溃,那么如果您尝试第一次防火练习,那么这大约需要多长时间运送那个傻瓜。

            另请注意:可重复性在测试中很重要!因此,让您的测试工具记录它使用的随机种子,并有一个参数以从相同的种子开始。此外,让它从已知的“基本状态”开始(ie,从服务器上的映像重新安装所有内容并从那里开始)或一些可重新创建的基本状态(ie,从该映像重新安装,然后根据测试工具作为参数的一些随机种子对其进行更改。)

            当然,如果该工具具有“每 20,000 个事件保存一次状态”和“在事件 # 之前停止”和“向前 1/10/100 个事件”之类的好东西,开发人员将不胜感激。这将极大地帮助他们重现问题、发现并解决问题。

            正如其他人所指出的,服务器是暴露给用户的另一件事。为自己获取一个包含 1,000,000 个 URL 的列表(来自服务器日志的 grep),然后将它们提供给您的随机数生成器。

            请记住:“系统连续 24 小时无误运行”并不意味着它已经准备好发货,它只是意味着它足够稳定,可以开始一些严肃的测试。在它能够做到这一点之前,QA 应该随意说“看,你的 POS 在栩栩如生的随机用户模拟下甚至不能持续 24 小时——你解决这个问题,我将花一些时间编写更好的工具。”

            哦,是的,最后一件事:除了“尽可能快和用力地敲击它”测试之外,还有能力“完全按照真正的用户 [可能是精神错乱的人,或者是键盘的婴儿/mouse] 会的。”也就是说,如果您正在执行随机用户事件;以非常快的打字员或非常快的鼠标用户可以做到的速度(偶尔延迟,以模拟一个慢速的人),除了“我的程序可以吐出事件的速度”之外。这是两种**非常不同*的测试类型,当发现错误时会得到截然不同的反应。

            【讨论】:

              【解决方案6】:

              Q1) 我发现具有大量并发性的分布式系统非常适合随机测试。很难为此类应用程序创建所有可能的场景,但随机测试可能会暴露您从未想过的问题。

              Q2)我想您可以尝试使用统计数据来建立围绕发现所有“错误”的置信区间。但实际的答案是:尽可能多地运行随机测试。

              Q3) 我发现随机测试很有用,但是在您编写了正常的单元测试、集成测试和回归测试之后。您应该将您的随机测试集成为正常测试套件的一部分,尽管可能是一个小运行。如果不出意外,您可以避免测试本身出现一些问题,并在团队使用不同的随机输入运行测试时获得一些覆盖率。

              Q4) 编写随机测试时,请确保将随机种子与测试结果一起保存。没有什么比发现您的随机测试发现错误,并且无法使用相同的输入再次运行测试更令人沮丧的了。确保您的测试也可以使用保存的种子执行。

              【讨论】:

                【解决方案7】:

                我同意 Federico 的观点 - 随机测试会适得其反。如果测试不能可靠地通过或失败,那么很难修复它并知道它已修复。 (当然,当您引入不可靠的依赖项时,这也是一个问题。)

                但是,您可能希望以其他方式确保您拥有良好的数据覆盖率。例如:

                • 确保您在 1900 年到 2100 年期间对每年每个月的开始、中间和结束进行测试(当然,如果这些测试适合您的代码)。
                • 使用多种文化,或“所有文化”(如果已知的话)。
                • 尝试“第 0 天”和“每个月结束后的一天”等。

                简而言之,仍然尝试很多值,但以编程方式和可重复的方式进行。您不需要在测试中尝试将每个值都作为文字 - 可以循环围绕测试的一个轴的所有已知值等。

                你永远不会得到完整的覆盖,但它至少是可重复的。

                编辑:我确定有 个地方可以使用随机测试,尽管可能不适用于单元测试。但是,在这种情况下,我想提出一些建议:使用一个 RNG 创建一个随机但已知的种子,然后用该值播种一个新的 RNG - 并记录它。这样,如果发生有趣的事情,您将能够通过使用记录的种子启动 RNG 来重现它。

                【讨论】:

                • 你提出了一些好观点。但是有一件事:您的意思是随机单元测试会适得其反,还是在所有测试期间都会适得其反?因为我拒绝买最后一个。但是,我会认为它对几乎所有测试都适得其反。 :-)
                • 我认为我们同意。这是一个非常罕见的案例,通过随机化做得很好,IMO。编辑一个特定的点...
                • 我使用了“随机”测试,但使用已知种子来使问题可重复。效果很好。
                【解决方案8】:

                关于第三个问题,我认为随机测试不非常适合单元测试。如果应用于同一段代码,单元测试应该总是成功,或者总是失败(即,由于错误引起的错误行为应该是可重现的)。但是,您可以使用随机技术生成大型数据集,然后在单元测试中使用该数据集;没有任何问题。

                【讨论】:

                  【解决方案9】:

                  哇,好问题!一些想法:

                  • 随机测试始终是一种很好的建立信心的活动,尽管正如您所提到的,它最适合某些类型的代码。
                  • 这是对性能可能与其执行次数或输入序列相关的任何代码进行压力测试的绝佳方法。
                  • 对于相当简单的代码或需要有限类型输入的代码,我更喜欢系统测试,明确涵盖所有可能的情况、每个不太可能或病态情况的样本,以及所有边界条件。

                  【讨论】:

                  • 好点。 +1。您确实应该明确区分随机值和极限值:它们不一样。
                  • 谢谢。你在跟踪我吗?还是我在跟踪你?还是只是偶然的机会? :-)
                  • Err... 不涉及跟踪 ;)。我们似乎有共同的编程兴趣。在 StackOverflow 上发生这种情况应该违反你的“最小惊讶原则”:)
                  猜你喜欢
                  • 1970-01-01
                  • 2010-11-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-09-13
                  • 1970-01-01
                  相关资源
                  最近更新 更多