3

考虑以下两种数据验证场景:

到处检查一切

确保每个接受一个或多个参数的方法都实际检查它们以确保它们在语法上是有效的。

优点

  • 非常精细的检查粒度。
  • 如果正在编写的代码是针对某种库的,我们会确保限制在使用它的开发人员未能提供有效数据时可能造成的损害。

缺点

  • 总是执行大多数时间不需要的检查是昂贵的。
  • 仍然有可能不时忘记添加支票。
  • 正在编写更多代码,因此需要维护。

利用 TDD 的优点

仅在数据从外部世界输入您的代码时验证数据。为了确保内部数据在语法上总是正确的,请创建测试来检查每个返回值的方法。确保如果输入有效数据,则退出有效数据。

利弊实际上与前一种方法的利弊互换了。

到目前为止,我正在使用第一种方法,但是由于我正在使用测试驱动开发,我想也许我可以使用第二种方法。

优点很明显,不过,我想知道它是否像第一种方法一样安全

4

5 回答 5

2

听起来第一种方法是契约驱动的,其中一个方面是您还需要验证从任何公共接口返回的内容是否符合契约。

但是,我认为这两种方法都是有效的,但非常不同。

TDD 仅部分处理公共接口,因为它应该检查每个输入是否经过正确验证,不幸的是,除非您在单独的函数中进行了所有验证,否则要进行充分测试,要确保这个函数 3 或 4 变得非常困难参数的有效性正在被适当地测试。无论哪种方法,您必须编写的测试数量都非常多。

如果您使用的是库,那么在可以从外部直接调用的每个函数中(在库之外),您将需要检查每个输入是否有效,并且根据合同处理无效输入,或者返回 null 或抛出异常。但是,它必须与文档一致。

一旦你验证了它,那么就没有理由强制验证私有函数,因为它们只能从库中调用,你应该验证你只是在处理有效数据。

不幸的是,无论如何,都需要进行大量测试。所有这些测试都是为了确保您没有任何意外问题,但这通常有助于证明编写和维护它们的成本是合理的。

至于你的问题,如果你的测试写得很好,并且你确保所有的有效性检查都完成了,那么它应该是安全的,但风险是,如果你认为它是安全的并且你的测试写得不好,那么它实际上会比没有测试更糟糕,因为假设您的测试写得很好。

我会使用这两种方法,直到你知道你的测试写得很好,然后就使用 TDD。

于 2009-09-04T17:17:50.677 回答
1

我的观点是,在第一种情况下,你的两个缺点胜过其他一切:

  • 总是执行大多数时间不需要的检查是昂贵的。
  • 正在编写更多代码,因此需要维护。

此外,从技术上讲,TDD 与这个问题无关,因为它不是一种测试技术。以后多...

为了减轻缺点,我强烈主张(正如我想你所说)将代码分成外部内部:外部是所有验证发生的地方。希望这只是内部的薄包装,以防止GIGO。一旦进入,数据就无需再次验证。

至于 TDD,我强烈主张(正如您现在所做的那样)使用它来开发您的代码,另外还有一个好处是留下一系列测试,成为回归测试套件。现在,您将自然地开发您的外部代码来执行强大的验证,并承诺轻松添加您最初可能忘记的任何检查。您的内部代码可以在假设它只处理有效数据的情况下进行开发,但 TDD 仍然会让您相信它会按照规范运行。

我是说我会采用第二种方法,正如我所描述的,无论我是否使用 TDD 进行开发(但 TDD 始终是我的首选)。

于 2009-09-06T21:45:12.270 回答
0

您的优点和缺点列表中缺少一项,这足以使单元测试成为比疯狂的参数检查更安全的方法。

您只需要考虑时间地点

对于单元测试,何时何地是:

  • 时间:在设计时
  • 其中:在应用程序代码之外的专用源文件中

对于矫枉过正的数据检查,它们是:

  • 何时:在运行时
  • 其中:纠缠在应用程序源代码中,通常使用断言。

这就是重点:单元测试涵盖的代码在您运行测试时在设计时检测错误,如果您是偏执狂和精神分裂症类型的测试员(最好的),您编写旨在打破任何可能的测试,检查每个数据边界和不正当的输入。您还可以使用代码覆盖工具来确保每个备选方案的每个分支都经过测试。您没有限制:测试位于它们自己的文件中,并且不会使应用程序混乱。如果您获得的测试行数是实际应用程序代码的 10 倍,没有运行时间损失,也没有可读性损失,这无关紧要。

另一方面,集成的过度杀伤测试在运行时检测错误. 在最坏的情况下,它会检测到用户系统上的错误,您对此无能为力(即使您听说过此错误发生)。此外,即使你是偏执狂,你也必须限制你的测试。断言不能占应用程序代码的 90%。它会引发可读性问题、维护问题,通常会导致严重的性能损失。那么你会在哪里停下来:只检查外部输入的参数?检查内部函数的每一个可能或不可能的输入?检查每个循环不变量?当流数据(全局、系统文件等)发生变化时,还要测试行为吗?您还必须意识到断言代码也可能包含一些错误。如果断言的公式执行除法怎么办。您必须确保它不会因除零错误等而导致?

另一个问题是,在许多情况下,您只是不知道断言失败时可以做什么。如果你在一个真正的入口点,你可以返回一些你的用户或 lib 用户可以理解的东西......当你检查内部函数时

于 2009-09-08T04:11:54.540 回答
0

优点很明显,不过,我想知道它是否和第一种方法一样安全。

这完全取决于你测试它的程度。

如果满足以下两个标准,这可能同样安全:

  • 向系统添加数据的每一种公开方式都经过完全验证
  • 每一个翻译数据的内部方法都经过了完全和充分的测试

但是,我质疑这会更容易,或者需要更少的代码。检查每个公共入口点所需的代码量将与验证每个方法所需的代码量非常相似。您将需要在入口点进行更多检查,因为他们必须检查否则可能会在内部检查的内容。

于 2009-09-04T17:05:46.397 回答
0

对于第二种方法,您需要两组好的测试。你不仅要检查

确保如果输入有效数据,则退出有效数据。

您还必须检查是否输入了无效数据,是否会引发异常。我想您仍然必须验证数据并在您有无效数据时退出。如果您不想在生产应用程序中出现讨厌的 ArgumentNullException 或其他神秘错误,这确实是唯一的方法。然而,TDD 确实可以提高所有检查的质量(尤其是模糊测试)。

于 2009-09-04T17:06:34.103 回答