测试:确定性还是不确定性?


16

最好有一个

  • 确定性测试套件,导致后续的相同测试
  • 非确定性测试套件,可能涵盖更多案例

示例: 您编写了一个测试套件来测试MVC应用程序中的控制器功能。在测试期间,控制器需要来自数据库的应用程序数据作为输入。有两种方法可以执行此操作:

  • 您可以硬编码选择测试数据库中的哪行作为输入(例如,第10行和第412行)
  • 您使用随机数生成器从数据库中伪随机地选择数据(随机数生成器选择了两行)

首先是确定性的:对于相同版本的代码,每次运行测试都应产生相同的结果。第二个是不确定的:测试套件的每次运行都有可能产生不同的结果。但是,随机选择的数据可能会更好地表示数据边缘情况。它可以模拟用户更好地为我们的控制器提供不可预测的数据吗?

选择一个而不是另一个的原因是什么?


5
该测试有时会失败。 martinfowler.com/articles/nonDeterminism.html

感谢您的链接。考虑到那篇文章,我觉得我需要澄清一下在此测试套件的上下文中非确定性意味着什么。由于数据是从数据库中随机选择的,因此默认情况下,馈入控制器的所有数据都是有效数据。这意味着在涉及非确定性时,测试套件中不存在误报。在某种程度上,这种随机性模拟了用户“随机”选择要在控制器中使用的数据。这不一定与本文讨论的确定性相同,对吗?
DCKing 2013年


10
@DCKing:考虑一下如果测试失败会发生什么。好的,您有一个错误。嗯,现在呢?在调试模式下再次运行它!成功的地方!就像它在接下来的一百次运行时一样,然后您将该问题记为宇宙射线打击。在测试中不确定性听起来绝对是行不通的。如果您认为需要在测试用例中覆盖更多的地面,请覆盖更多的地面。用设置的种子初始化RNG,并使用一致的随机值运行“测试”几百次。
Phoshi 2013年

1
(最终绕到了我可以正确搜索Twitter的机器上-“ 该测试有时有时会失败 ”是来自Twitter上的#FiveWordTechHorrors-希望正确地归功于此)

Answers:


30

当每次运行测试套件都可能产生不同的结果时,该测试几乎是毫无价值的-当套件显示错误时,您很有可能无法重现它,而当您尝试修复该错误时,错误,您无法验证您的修复程序是否有效。

因此,当您认为需要使用某种随机数生成器来生成测试数据时,请确保始终使用相同的种子初始化生成器,或者将随机测试数据保存在文件中,然后再将其输入测试中,因此您可以使用与之前运行完全相同的数据再次重新运行测试。这样,您可以将任何非确定性测试转换为确定性测试。

编辑:使用随机数生成器来选择一些测试数据是恕我直言,有时这是太迟于选择好的测试数据的迹象。而不是抛出100,000个随机选择的测试值,并希望这足以偶然发现所有严重的错误,更好地利用您的大脑,挑选10到20个“有趣”的案例,并将其用于测试套件。这不仅可以提高测试质量,还可以提高套件的性能。


感谢您的回答。您对我对问题的评论有何看法?
DCKing 2013年

1
@DCKing:如果您真的认为随机生成器会比您选择更好的测试用例更好(我对此表示怀疑),请使用它一次查找程序失败的测试数据组合,然后将这些组合放入“硬编码”部分您的测试套件。
布朗

再次感谢。更新了我的答案,以便它似乎不仅适用于MVC应用程序。
DCK

1
在某些UI上下文中(例如,带有控制器输入的游戏),具有生成随机键输入的测试程序可能对压力测试很有用。他们可以发现用故意输入很难发现的缺陷。
使机器人

@StevenBurnap:好吧,据我所知,我认为OP考虑到了更多常规回归测试。当然,我同意,压力测试是一种特殊情况,即使您不使用随机生成器,压力测试也可能与硬件有关,并且会导致不确定的行为。这是MichaelT在问题下方的第一条评论中与之链接的文章中所描述的。即使在使用随机输入的压力测试中,也至少可以尝试使用定义的随机种子使行为更具确定性。
布朗

4

确定性和非确定性都有

我将它们划分如下:

单元测试。

这些应该具有确定性,可重复的测试,每次都具有完全相同的数据。单元测试伴随特定的,隔离的代码节,并且应该以确定性的方式对其进行测试。

功能和输入压力测试。

这些可以使用非确定性方法,但要注意以下几点:

  • 清楚地描述并指出了这一事实
  • 记录所选的随机值,可以手动重试

3

都。

确定性和非确定性测试对您的套件具有不同的用例和不同的价值。通常,不确定性不能提供与确定性测试相同的精度,确定性测试已逐渐发展为“不确定性测试没有价值”。这是错误的。它们可能不太精确,但也可以更广泛,这有其自身的优势。

让我们举个例子:编写一个对整数列表进行排序的函数。您会发现有用的一些确定性单元测试是什么?

  • 空清单
  • 仅包含一个元素的列表
  • 具有所有相同元素的列表
  • 具有多个唯一元素的列表
  • 包含多个元素的列表,其中一些元素是重复的
  • 名单用NaNINT_MININT_MAX
  • 已部分排序的列表
  • 包含10,000,000个元素的列表

那只是一个排序功能!当然,您可能会争辩说其中一些是不必要的,或者可以通过非正式推理排除其中一些。但是我们是工程师,而且我们已经看到非正式的推理在我们的脸上爆炸。我们知道我们不够聪明,无法完全理解已构建的系统或完全无法掌握复杂性。这就是为什么我们首先编写测试的原因。添加非确定性测试只是说我们可能不一定足够聪明就可以先验地了解所有好的测试。通过将半随机数据放入函数中,您更有可能找到遗漏的边缘情况。

当然,这也不排除确定性测试。非确定性测试有助于发现大量程序中的错误。但是,一旦发现了错误,就需要一种可再现的方式来证明已修复它。所以:

  • 使用不确定性测试来查找代码中的错误。
  • 使用确定性测试来验证代码中的修复程序。

请注意,这意味着很多有关单元测试的可靠建议不一定适用于不确定性测试。例如,它们必须快。低级属性测试应该很快,但是诸如“模拟用户随机单击您网站上的按钮并确保您永远不会出现500个错误”之类的不确定性测试应该优先考虑全面性而不是速度。只需进行独立于您的构建过程的测试,这样就不会减慢开发速度。例如,在自己的专用登台盒上运行它。


-1

您实际上并不想要确定性与非确定性。

您可能想要的是“始终相同”与“并非始终相同”。

例如,您的内部版本号可能会随着每次内部版本的增加而增加,并且当您需要一些随机数时,您可以将内部版本号作为种子初始化一个随机数生成器。因此,每次构建时,您都使用不同的值进行测试,从而使您有更多的机会发现错误。

但是,一旦发现错误,您所需要做的就是以相同的内部版本号运行测试,并且该测试是可重复的。


1
或者,如果您没有要使用的内部版本号,请将种子的初始值放在测试运行的输出中,以便您可以再次使用相同的种子重新运行测试。
RemcoGerlich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.