单元测试是不是使 Debug.Assert() 变得不必要?

Posted

技术标签:

【中文标题】单元测试是不是使 Debug.Assert() 变得不必要?【英文标题】:Does Unit Testing make Debug.Assert() unnecessary?单元测试是否使 Debug.Assert() 变得不必要? 【发布时间】:2009-05-04 19:54:36 【问题描述】:

我已经准备好麦康奈尔的“Code Complete”已经有一段时间了。现在我在 Hunt & Thomas 的“The Pragmatic Programmer”中再次阅读它:使用断言! 注意:不是单元测试断言,我的意思是Debug.Assert()

遵循 SO 问题 When should I use Debug.Assert()? 和 When to use assertion over exceptions in domain classes 断言对开发很有用,因为可以很快找到“不可能”的情况。而且它们似乎很常用。据我了解断言,在 C# 中,它们通常用于检查输入变量的“不可能”值。

为了尽可能保持单元测试的简洁和隔离,我使用nulls 和“不可能”的虚拟输入(如空字符串)来提供类和方法。

此类测试是明确记录的,它们不依赖于某些特定输入。 注意:我正在练习 Meszaros 的“xUnit 测试模式”所描述的 Minimal Fixture。

这就是重点:如果我有一个断言来保护这些输入,它们会破坏我的单元测试。

我喜欢断言式编程的想法,但另一方面我不需要强制它。目前我想不出Debug.Assert() 有什么用处。也许我想念什么?你有什么建议可以真正有用吗?也许我只是高估了断言的有用性?或者也许我的测试方式需要重新审视?

编辑:Best practice for debug Asserts during Unit testing 非常相似,但它没有回答困扰我的问题:如果我像我描述的那样进行测试,我是否应该关心 C# 中的Debug.Assert()?如果是,它们在什么情况下真的有用?在我目前的观点中,这样的单元测试将使Debug.Assert() 变得不必要。

另外一点:如果你真的这么认为,这是一个重复的问题,只需发表一些评论。

【问题讨论】:

请注意,Visual Studio 的测试框架将 Debug.Assert 失败视为单元测试失败。 【参考方案1】:

理论上,您是对的 - 详尽的测试使断言变得多余。理论上。实际上,它们对于调试您的测试以及捕捉未来开发人员可能会尝试使用不符合其预期语义的接口的尝试仍然很有用。

简而言之,它们只是用于与单元测试不同的目的。他们的存在是为了捕捉那些在编写单元测试时本质上不会发生的错误。

我建议保留它们,因为它们提供了另一个级别的保护,防止程序员出错。

它们也是一种本地错误保护机制,而单元测试在被测试代码之外。在压力下“不经意间”禁用单元测试比禁用一段代码中的所有断言和运行时检查要容易得多。

【讨论】:

"... 用于捕捉未来开发人员可能会尝试使用不符合其预期语义的接口的尝试。"。嗯......我可以想到一些利基,它们在哪里很有用:当接口的语义不明显时,放置一个断言会使这一点非常明确。 再想一想,这很有意义:以这种方式使用断言在开发过程中是有益的,旨在发现编程错误(而不是来自用户/数据库/webservice/...的一些输入错误) ) 没错。这是合约编程的另一个方面,合约不能在编译时强制执行,但运行时可以捕获违规行为。【参考方案2】:

我通常看到断言用于内部状态的健全性检查,而不是参数检查之类的东西。

IMO 对可靠 API 的输入应通过检查来保护,无论构建类型如何。例如,如果公共方法需要一个介于 5 到 500 之间的数字的参数,则应该使用 ArgumentOutOfRangeException 对其进行保护。就我而言,快速失败并且经常使用异常失败,特别是当一个参数被推送到某个地方并且稍后使用时。

但是,在对内部瞬态状态进行完整性检查的地方(例如,在循环期间检查某些中间状态是否在合理范围内),似乎 Debug.Assert 更适用。尽管传递了有效的参数,但当您的算法出错时,您还打算做什么?抛出 EpicFailException? :) 我认为这是 Debug.Assert 仍然有用的地方。

我仍未决定两者之间的最佳平衡。自从我开始单元测试以来,我已经停止在 C# 中大量使用 Debug.Asserts,但 IMO 仍然有它们的位置。我当然不会使用它们来检查 API 使用的正确性,但是很难到达地方进行健全性检查?当然。

唯一的缺点是它们可以弹出并停止 NUnit,但您可以编写一个 NUnit 插件来检测它们,并使任何触发断言的测试失败。

【讨论】:

Lol at EpicFailException...我将不得不在我的代码中使用它(结合 Over9000Exception)。 “例如,在循环期间检查某些中间状态是否在合理范围内” ... 检查循环不变量的断言!那是个很好的观点。这是我无法通过单元测试真正检查的东西(至少不能通过或多或少的模拟/存根和引入一些间谍变量)。 那些弹出窗口很烦人......不知道会发生什么,当断言在 Windows 服务中发生时......可能值得另一个问题 ;-)【参考方案3】:

我将单元测试和断言用于不同的目的。

单元测试是一种自动化实验,表明您的程序(及其部分)按规定运行。就像在数学中一样,只要你不能尝试所有可能的输入组合,实验就不是证明。没有什么比即使使用单元测试,您的代码也会有错误这一事实更好地证明这一点。数量不多,但会有。

断言用于在运行时捕获通常不应该发生的狡猾情况。也许你听说过前置条件、后置条件、循环不变量和类似的东西。在现实世界中,我们通常不会通过正式过程来实际证明(通过形式逻辑)如果满足先决条件,则一段代码会产生指定的后置条件。这将是一个真正的数学证明,但我们通常没有时间对每种方法都这样做。但是,通过检查是否满足前置条件和后置条件,我们可以在更早的阶段发现问题。

【讨论】:

我不做整个 100% 单元测试的事情,但我认为覆盖前置条件和后置条件是它的重点;真正涵盖所有边缘案例的自动化测试,提供所有可能输入的数据,并测试所有输出的数据。 断言没有明确地用于检查前置条件和后置条件,但仍然可以用来模拟它。这是断言有用的另一点。【参考方案4】:

如果您正在进行详尽的单元测试,涵盖您可能遇到的所有奇怪的边缘情况,那么我认为您不会发现断言非常有用。大多数不进行单元测试的人会在您使用测试时设置断言来建立类似的约束。

【讨论】:

对我来说这意味着:断言是好的,当没有单元测试到位时。虽然它们不能替代单元测试,但总比没有好。 坦率地说,我会说断言(以大多数人使用的形式)与单元测试几乎相同。人们通常坚持使用它们来验证进入函数的输入是他们可以处理的东西,并且函数的输出是他们期望的,这正是测试该函数时单元测试应该做的事情.最大的区别是您的单元测试套件通常与您的代码不在同一个位置,而断言是内联的。 当然,断言有时还检查比单元测试更细粒度的东西,例如确保正确分配内存或正确建立连接。 “而断言是内联的”。这让我想到了另一点:断言在代码中具有一些文档特征。他们在向开发人员大喊:不要那样做! 如果我愿意的话,他们仍然会破坏我的测试。断言连接已正确打开,我需要在单元测试中存根/模拟/伪造此行为,这可能很麻烦。【参考方案5】:

我认为在这种情况下,单元测试背后的想法是将这些断言转移到测试用例中,以确保,而不是让 Debug.Assert(...) 被测代码处理它而不会抛出(或确保它吐得正确)。

【讨论】:

以上是关于单元测试是不是使 Debug.Assert() 变得不必要?的主要内容,如果未能解决你的问题,请参考以下文章

Debug.Assert 与异常抛出

使代码内部化,但可用于其他项目的单元测试

Debug.Assert 与异常

C#:Release-Build-DL​​L 中的 Debug.Assert() 似乎由 Debug-Build-Project 激活

Kotlin:使内部函数对单元测试可见

单元测试类是不是应该与其余代码一起受版本控制?