使用随机输入测试最佳实践

时间:2008-11-01 20:28:15

标签: unit-testing language-agnostic

注意:我提到接下来几段作为背景。如果你只是想要一个TL; DR,可以跳到编号的问题,因为它们只与这些信息间接相关。

我正在编写一个python脚本,用POSIX日期(以及其他东西)做一些事情。单元测试这些看起来有点困难,因为可以遇到的日期和时间范围很广。

当然,尝试测试每一个可能的日期/时间组合是不切实际的,所以我想我会尝试一个随机化输入的单元测试,然后报告测试失败时的输入。从统计学的角度来说,我认为如果我试图考虑所有潜在的问题区域(由于缺少的东西)或测试所有情况(由于纯粹的不可行性),我可以实现更多的测试完整性,假设我运行它足够的时间。

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

  1. 什么类型的代码是随机测试的理想选择?什么类型的代码不是?
    • 如何确定使用随机输入运行代码的次数?我问这个是因为我想要一个足够大的样本来确定任何错误,但是不想等待一周才能得到我的结果。
    • 这些类型的测试是否非常适合单元测试,还是有其他类型的测试可以很好地适应?
    • 有没有其他最佳做法来做这种事情?
  2. 相关主题:

9 个答案:

答案 0 :(得分:12)

我同意费德里科 - 随机测试适得其反。如果测试不能可靠地通过或失败,则很难修复并知道它是固定的。 (当你引入不可靠的依赖时,这也是一个问题。)

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

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

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

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

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

答案 1 :(得分:6)

关于第3个问题,在我看来,随机测试非常适合单元测试。如果应用于同一段代码,则单元测试应始终成功,或者始终失败(即,由于错误导致的错误行为应该是可重现的)。但是,您可以使用随机技术生成大型数据集,然后在单元测试中使用该数据集;这没什么不对。

答案 2 :(得分:3)

哇,好问题!一些想法:

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

答案 3 :(得分:1)

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

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

Q3)我发现随机测试很有用,但之后你已经编写了正常的单位,积分和回归测试电池。您应该将随机测试作为正常测试套件的一部分进行集成,尽管可能只是一小部分。如果没有别的,你可以避免测试本身的位腐烂,并在团队使用不同的随机输入运行测试时获得一些小的覆盖。

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

答案 4 :(得分:1)

一些事情:

  • 通过随机测试,您无法确定一段代码是什么,但您可以告诉它是什么。
  • 随机测试更适合具有随机输入的事物 - 一个主要的例子是任何暴露给用户的事物。因此,例如,随机点击&您的应用程序(或操作系统)上的类型是对一般稳健性的良好测试。
  • 同样,开发人员也算作用户。因此,从框架中随机组装GUI的东西是另一个很好的候选者。
  • 同样,你不会以这种方式找到所有的错误 - 你要找的是“如果我做了一百万个糟糕的事情,那么它们中的任何一个都会导致系统损坏吗?”如果没有,您可以对您的应用/操作系统/ SDK /任何可能持续几天的用户感受到一定程度的信心。
  • ...但是,更重要的是,如果您的随机拍打上层测试应用程序可能会在大约5分钟内崩溃您的应用程序/操作系统/ SDK,那么这将是您在第一次消防演习之前有多长时间,如果您尝试运送那个傻瓜。

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

当然,如果该工具具有诸如“保存状态每20,000个事件”和“在事件#之前停止”和“向前迈步1/10/100事件”这样的工具,开发人员将会很感激。这将极大地帮助他们重现问题,找到并修复它。

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

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

哦,是的,最后一件事:除了“尽可能快地和尽可能地坚硬”测试之外,还有能力做到“真正的用户[可能是一个疯狂的人,或者是一个婴儿束缚了键盘/鼠标]会的。“也就是说,如果你正在进行随机的用户事件;以非常快的打字员或非常快的鼠标用户可以做的速度(偶尔延迟,模拟一个慢人),以及“我的程序可以快速吐出事件”的速度来做它们。这些是两种非常不同的*类型的测试,当发现错误时会产生非常不同的反应。

答案 5 :(得分:1)

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

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

答案 6 :(得分:0)

这可能稍微偏离主题,但如果您使用.net,则有Pex,它执行与随机测试类似的操作,但通过尝试生成“随机”测试用例更直观通过代码练习所有路径。

答案 7 :(得分:0)

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

  

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

     

如果你解决了oracle问题,那么   可以比简单更进一步   随机输入生成。您可以   选择输入分布   你的软件的特定部分得到   比简单运用更多   随机的。

     

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

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

如果您随机选择ab   int范围,你行使Foo 50%   时间,Bar 25%的时间和   Foobar 25%的时间。这有可能   您将在Foo中找到更多错误   而不是BarFoobar

     

如果您选择a,那就是   负66.66%的时间,Bar和   Foobar得到的锻炼比锻炼更多   你的第一次发行。的确如此   每个分支都有三个分支   33.33%的时间。

     

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

答案 8 :(得分:0)

随机测试具有巨大的优势,可以以极低的成本生成单个测试。即使您只有部分oracle(例如,软件崩溃了吗?)

,情况也是如此

在复杂系统中,随机测试会发现很难通过任何其他方式找到的错误。想想这对安全测试意味着什么:即使你不进行随机测试,黑帽子也会,你会发现你错过的错误。

随机测试的一个迷人的子领域是随机差分测试,其中两个或多个应该显示相同行为的系统用共同输入激励。如果他们的行为不同,则会发现一个错误(在一个或两个中)。这已经被应用于编译器的测试,并且总是发现任何编译器中以前没有遇到过该技术的错误。即使您只有一个编译器,您也可以在不同的优化设置上尝试查找不同的结果,当然崩溃总是意味着错误。