如何测试测试?


53

我们测试代码以使其更正确(实际上,正确的可能性较小)。但是,测试也是代码-它们也可能包含错误。而且,如果您的测试有错误,则很难使您的代码变得更好。

我可以想到测试中三种可能的错误类型:

  1. 逻辑错误,当程序员误解了手头的任务,并且测试按照他认为应该做的事情进行时,这是错误的;

  2. 基础测试框架中的错误(例如,泄漏的模拟抽象);

  3. 测试中的错误:测试的执行与程序员认为的稍有不同。

类型(1)错误似乎无法防止(除非程序员只是……变得更聪明)。但是,(2)和(3)可能很容易处理。您如何处理这些类型的错误?您有什么特殊策略可以避免它们吗?例如,您是否编写了一些特殊的“空”测试,仅检查测试作者的预设?此外,您如何调试已损坏的测试用例?


3
我读过的每一篇关于模拟的入门文章似乎都在解决这个问题。一旦开始模拟事物,测试似乎总是比他们正在测试的代码更复杂。显然,在测试实际代码时不太可能出现这种情况,但是当您尝试学习时,它会令人沮丧。
2010年

@ Carson63000如果这是一个简单的测试,并且使用经过测试的模拟对某些东西进行测试,那么复杂度是可以控制的(我认为)。
mlvljr 2010年

13
但是那您如何测试测试呢?
ocodo

+1。项目1可能是需求错误。只能通过查看要求来防止。除非他们也是需求分析员,否则可能不在程序员的手中
MarkJ 2012年

@ocodo:您观看观察者的方式相同。:)
Greg Burghardt

Answers:


18

测试已经过测试。由于测试仅检测代码与我们的期望之间的差异,因此设计使测试免受bug的影响。如果有问题,我们会出错。错误可能在代码中,也可能在测试中具有相同的可能性。

  1. 有一些技术可以阻止您在代码和测试中添加相同的错误:

    1. 客户应与实施者不同。
    2. 首先编写测试,然后编写代码(例如在“测试驱动开发”中)。
  2. 您无需测试基础平台。这些测试不仅行使您编写的代码,而且它们也从平台运行代码。虽然您不一定要在测试平台中捕获错误,但是很难编写总是会在平台中隐藏错误的代码和测试,换句话说,很难在测试/代码和在平台上,并且您创建的每个测试的概率都会降低。即使您尝试执行此操作,您也将面临艰巨的任务。

  3. 您可能在测试中存在错误,但是通常会很容易捕获它们,因为测试已通过开发的代码进行了测试。在代码和测试之间,您将获得自我实施的反馈。两者都可以预测接口的特定调用的行为。如果响应不同,则无需在代码中包含错误。您也可能在测试中出现错误。


很好的答案。我喜欢在测试和代码之间进行自我强化循环的想法,并喜欢这样的观察,即编写一致的测试会很难隐藏底层平台中的错误。
Ryszard Szopa 2010年

这不会阻止基于关于实际代码应该做什么的错误假设来创建测试。这可能导致非常讨厌的错误在测试过程中未被发现。防止这种情况的唯一方法是让第三方编写的测试与编写实际代码的组织完全没有关系,因此在解释需求文档时,它们不会“污染”彼此的想法。
jwenting

24

尝试使各个测试尽可能小(简短)。

首先,这应该减少了创建错误的机会。即使您设法创建一个,也更容易找到。单元测试应该是小型且特定的,对故障和偏差的容忍度较低。

最后,这可能只是经验问题。您编写的测试越多,您做的越好,进行糟糕测试的机会就越少。


3
如果测试需要一些相当复杂的设置怎么办?有时,这些事情不在您的控制之下。
Ryszard Szopa

好吧,我猜想复杂的设置是测试的“初始条件”。如果失败,则所有测试都将失败。实际上,我现在正在从事这样的项目,并且从未使用过单元测试的人经常问同样的事情。.直到我们解释了单元测试的真正含义是:项目的复杂性。
汉尼拔·莱克特博士

检查此“初始条件”是否满足的最佳方法正是我的问题所在。您是否为此编写了单独的测试?还是只是假设如果这种情况不成立,测试就会失败?如果安装程序不是“灾难性的”坏,只是略有改变,情况又如何呢?
Ryszard Szopa 2010年

2
如果初始条件不正确,您的测试应该会失败,这就是重点。处于状态时A,您期望得到结果B。如果您没有状态A,则测试应该会失败。到那时,您可以调查它为什么失败,不良的初始条件或不良的测试,但是在两种情况下都应该失败。即使是如你所说,“微客”(即"A" => "B""a" => "b"但从来没有"a" => "B"或您的测试是坏的)。
汉尼拔·莱克特博士

19

一种策略是在测试代码之前编写测试,并确保测试出于正确的原因而首先失败。如果使用TDD,则至少应获得此测试级别的测试。

测试套件质量的更详尽的方法是使用变异测试


2
并且您的测试由于正确的原因而失败。
Frank Shearar

@弗兰克-是的。我将其添加到答案中。
唐·罗比

您正在为要测试的新行为添加新的测试。不要添加到现有测试中。
Huperniketes 2010年

@DonRoby,您发现突变测试在实践中有用吗?您在测试用例中发现了哪些缺陷?
dzieciou 2012年

4

对于#1和#3:单元测试不应包含任何逻辑,如果这样做,则可能会在单元测试中测试多个项目。单元测试的一种最佳实践是每个单元测试只有一个测试。

观看Roy Osherove的这段视频,详细了解如何编写单元测试。


广告#3-我同意测试应尽可能简单,并且不应包含任何逻辑。但是,在创建测试所需的对象时,请考虑一下测试的设置阶段。您可能会创建一些错误的对象。这就是我正在考虑的问题。
Ryszard Szopa 2010年

当您说“轻微错误的对象”时,您是说对象状态不正确还是对象的实际设计不正确?对于对象状态,您可能可以编写测试以检查其有效性。如果设计错误,则测试应失败。
Piers Myers 2010年

3

就#1而言,我认为对这方面的内容进行配对/代码审查是一个好主意。进行预设或弄错是很容易的,但是如果您必须解释测试的目的,意义所在,那么如果您瞄准错误的目标,您就更有可能接手。


2

必须指出何时应该停止尝试进行单元测试。应该知道何时划界。我们应该编写测试用例来测试测试用例吗?写入测试用例的新测试用例如何?我们将如何测试它们?

if (0 > printf("Hello, world\n")) {
  printf("Printing \"Hello, world\" failed\n");
}

编辑:更新了注释所建议的解释。


-1什么?这似乎无关紧要。
可替代

2
必须指出何时应该停止尝试进行单元测试。应该知道何时划界。我们应该编写测试用例来测试测试用例吗?写入测试用例的新测试用例如何?我们将如何测试它们?
2010年

2
Process Brain在尝试推断您的陈述时提出了EInfiniteRecursion ...
Mason Wheeler 2010年

用您的评论替换您的答案,您将获得+1
自我说明-想起一个名字,2010年

3
公平地说,您的榜样是一个稻草人。您正在C语言库中测试printf()子系统,而不是实际的调用printf()的程序。但是,我确实同意,必须在某个时候中断测试的递归测试。
蒂姆·波斯特

2

嘿。
您必须申请:

  • 您的产品
  • 您对该产品的测试。

当您对产品运行测试时,实际上您并不是被测试本身所吸引,而是被产品和测试之间的交互所吸引。如果测试失败,并不表示该应用程序存在错误。它说产品与测试之间的交互并不成功。现在,确定出了什么问题是您的工作。可以是:

  • 应用程序的行为不符合您的预期(测试中表达了这种预期)
  • 应用程序的行为正确,只是您没有正确记录此行为(在测试中)

对我而言,测试失败不是简单的反馈,那是错误的。它指示存在不一致,并且我需要检查两者以检查是否出错。最后,我负责验证应用程序是否正确,测试只是一种突出显示可能值得检查的区域的工具。

测试仅检查应用程序的某些部分。我测试应用程序,我测试测试。


2

测试不应足够“智能”以包含错误。

您正在编写的代码实现了一组规范。(如果X则为Y,除非Z则为Q等)。除非Z在这种情况下为Q,否则所有测试都应尝试确定X确实是Y。这意味着所有测试应做的是设置X并验证Y。

但这可能无法涵盖所有​​情况,您会说对的。但是,如果您使测试足够“聪明”,以至于X应该只Y而不是Z才应该Y,那么您基本上是在重新实现测试中的业务逻辑。这是有问题的,原因是我们将在下面更深入地介绍。您不应该通过使第一个测试“更智能”来提高代码覆盖率,而应该添加第二个哑测试,该测试设置X和Z并验证Q。这样,您将拥有两个测试,一个测试涵盖了一般情况(有时也称为“幸福道路”),并且作为单独的测试覆盖了边缘情况。

造成这种情况的原因很多,首先是如何确定失败的测试是由于业务逻辑中的错误还是由于测试中的错误?显然,答案是,如果测试尽可能简单,则它们不太可能包含错误。 如果您认为您的测试需要测试,那么您就测试错了

其他原因包括您只是在复制工作(如我已经提到的,编写足够智能以在单个测试中行使所有可能性的测试基本上是在复制您首先要测试的业务逻辑)那么测试应该易于更改以反映新的需求,测试可以作为一种文档(它们是一种正式的方式来说明被测设备的规格是什么),等等。

TL:DR:如果您的测试需要测试,那就错了。 写愚蠢的测试


0

这不是一个答案(我没有发表评论的特权),但是想知道您是否忘记了开发测试用例的其他原因...
一旦弄清了测试中的所有错误,就可以轻松地对应用程序进行回归测试。自动化测试套件将帮助您在集成之前及早发现问题。需求的更改相对较容易测试,因为更改可以成为较新的,通过的较旧测试用例的经过更改的版本,而较旧的用例仍会保留故障。


0

简短的答案:生产代码对测试进行测试

将此与经济学中使用的贷方/借方模型进行比较。机制很简单-如果贷方与借方有所不同,则说明存在问题。

单元测试也是如此-如果测试失败,则表明出了点问题。它可能是生产代码,但也可能是测试代码!这最后一部分很重要。

请注意,单元测试无法找到您的类型(1)错误。为了避免此类错误,您需要其他工具。

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.