Nunit: 自 3.10 以来,捕获 AssertionException 的测试失败。

创建于 2018-03-14  ·  22评论  ·  资料来源: nunit/nunit

````
尝试
{
Assert.True(false, "错误信息");
}
catch (AssertionException e)
{
Trace.WriteLine("忽略。");
}

        Trace.WriteLine("Test Passed");

````

在 Nunit 3.9.0 中,当从 Visual Studio 测试资源管理器运行时,此测试成功并在测试资源管理器中标记为通过。 在 Nunit 3.10 中,测试用例在测试浏览器概述中被标记为失败。 最后跟踪行在两个版本中都执行。

question

最有用的评论

@fluffynuts很久以前,当我第一次写Assert.Catch它只是捕获了任何异常,如果没有抛出异常也不会产生错误。 不幸的是,情况发生了变化,我们不再有Assert.Throws的“中立”等价物。 不过,你可以写一个。 这是基于Assert.Throws的代码的未经测试的版本。 它只处理同步代码,但您可以使用Assert.Throws附加代码轻松地为异步扩展它。

```C#
公共静态无效异常 SafelyCatchAnNUnitException(TestDelegate 代码)
{
异常被捕获Exception = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
``

该方法将返回抛出的任何异常,清除 NUnit 的 assert 方法留下的错误的任何残余。 如果没有抛出异常,则返回 null。

还可以在代码中的更高级别使用IsolatedContext()以从中检索实际测试结果。 这将允许您完全在测试结果级别进行测试,而无需提及任何异常。 我已经考虑过为这种类型的测试编写一个库,我认为这会优于 NUnit 目前进行自己测试的方式。

所有22条评论

您的测试正在测试关于 NUnit 如何工作的两个命题:

  1. 所有断言失败都会抛出一个 AssertionException。
  2. 捕获异常可以防止报告错误。

碰巧的是,第一条语句大部分是正确的,但在多个断言块中或在发出警告时不成立。 第二个根本不是真的。 当您捕获异常时,故障已经被记录下来。 这是 NUnit 内部行为的变化,只有在捕获这些异常时才能观察到。 多年来,我一直在告诉人们不要依赖这种行为,因为这只是实施过程中的一个意外。

我意识到您的示例代码仅用于演示您看到的问题。 您能否提供一个示例,说明您在捕获异常时实际尝试执行的操作? 我很确定我们可以帮助您找到更好的解决方案。

感谢您在这么短的时间内回复我。 当事情仍然不稳定时,我会使用该机制。 所以有时软件不能正常工作。 在这些情况下,我将此报告给开发人员。 与此同时,我们扩展了测试以解决这种不稳定的行为。 所以测试通过而不是在这个问题上失败。

我们尝试为此使用 Warn.If() 方法,但是这会在测试资源管理器中将结果设置为“测试跳过”。 这会导致报告失败,因为根本没有跳过测试。 所以我们选择实现断言捕获并跟踪警告。 我知道,这并不理想,但效果很好。 至少直到上次更新。

我很好奇这个改变是否是故意的,在那种情况下我知道我必须改变一些事情。

Warn.If工作正常... __except__ 在测试资源管理器下。 😢 问题是测试资源管理器知道通过和失败,但不知道警告。 在他们实施警告结果状态之前,我们必须将我们的警告结果转换为他们可以理解的内容。 FWIW 我们的 Inconclusive 结果状态也有类似的问题。

我们所做的更改是有意的,并且已经计划了很长时间。 我们不想太多时间依赖异常,我们希望以某种不会停止测试的方式报告断言的结果——包括最终通过的断言。 我们已经进行了一些更改,以减轻这种对像您这样的用户的影响,但这似乎没有任何缓解可能。

如果你有片状测试,多年来的标准是忽略它们。 不幸的是,许多团队“忽略了被忽略的测试”,在这种情况下,这不是一个好的选择。 如果您可以训练您的团队认真对待警告——事实上,几乎与错误和失败一样认真——那么您可以使用警告状态来突出脆弱性。 另一种可能性是将忽略与直到属性一起使用,因此如果在特定日期之前未修复,它将变为失败。

顺便说一句,当我执教团队时,我通常会不断更新忽略测试的大型可见显示。 在大多数团队中,每个人都知道谁负责什么,所以没有必要真正召集负责不稳定测试的人。 当每个人都看到问题时,他们往往会得到解决。

@svanimpelen您是否尝试过使用Retry属性进行片状测试? 它将允许您多次运行它们以使它们通过。

至于在资源管理器中导致跳过测试的警告,我们已经与 Visual Studio 团队讨论过,允许我们指定比他们目前支持的 MSTest 的有限数量更多的结果状态。 他们喜欢这个想法,所以希望我们将来会看到那里的改进。 在那之前,你需要忍受跳过。 也就是说,跳过会导致警告。 我假设你希望警告非常明显😄

我们的许多方法都记录了 API 在失败时抛出 AssertionException 的情况:

https://github.com/nunit/nunit/blob/d03e93c8f25170a8ff48e80863a3fe6fd294613a/src/NUnitFramework/framework/Assert.That.cs#L40 -L43

这份文档,加上 NUnit 的长期行为,难道不是 3.10 的重大变化吗?
除非我遗漏了什么,我们至少应该删除现在不正确的文档。

我同意文档现在可能会产生误导,因为我们已经引入了多个断言块。 也就是说,在这种情况下仍会抛出AssertionException ,不同之处在于,即使捕获了异常,测试现在在 Visual Studio 中仍报告为失败。 我不认为这是一个重大变化。 用户依赖于碰巧起作用的无证行为。 对我来说,这与用户依赖测试碰巧按字母顺序运行来对测试进行排序的事实没有什么不同。

我们可能应该更新文档。 我们可以删除有关抛出异常的信息,而只是声明错误条件未通过测试。 和/或者,我们可以声明在多断言块中不会抛出异常? 就个人而言,我认为抛出的异常类型是内部细节,所以我更喜欢第一个选项。

我认为这是对的。 如果要在任何地方记录异常的使用,则可以在有关内部结构的 wiki 部分中进行记录。

当方法文档说将抛出异常时,我认为明确地说可以在调用站点捕获异常。 C# 没有检查异常和 XML 文档是我们一直在沟通方法契约的那部分的方式。 由于据我所知,语言“抛出 X”一直意味着该方法无法捕获它,因此我仍然有点担心其他人是如何接受它的。 也许维基中的一个重大更改说明是安全的?

我喜欢从 XML 文档中删除对 AssertionException 的所有引用以及创建一个 wiki 页面警告人们不要使用 AssertionException 的想法。

💭如果捕获AssertionException总是一个错误,我们是否可以考虑将该类型内部化?

@jnm2。

我同意您关于 XML 文档的重要性的看法,但事实是这些文档 __not__ 到目前为止我们一直在传达合同的一部分。 我实际上认为您可能是第一个提出建议的人,我认为这是一个好主意。

但是我认为您必须认识到,开始以这种方式思考它们将涉及更改许多错误的评论! 我不认为我们可以将这些文档更改中的每一个都视为重大更改。 在个案的基础上,我们可以合理地将任何潜在的行为变化视为破坏性的,但这是不同的。 文档更改不应中断。

这就提出了一个问题,即重大更改列表中应该包含哪些内容。 如果我理解你过去的评论,我认为你倾向于记录任何可能破坏任何用户的东西。 我更喜欢记录我们认为会破坏很多用户的事情。

这是我如何看待差异的 - 其他人可能不同意......记录__一切可能破坏__对我来说就像CYA一样,就像管理层有时沉迷于谁因失败而受到指责而不是预防失败本身时所沉迷的那种CYA。 如果我们记录了所有内容,那么我们可以说......“看,我们被覆盖了。” 如果我们只记录我们认为重要的内容,那么我们冒着犯错和不得不道歉的风险,但我们为普通用户提供了更容易理解的更改集。 我更喜欢后者。

有时捕获断言异常是安全的。 例如,我不久前进行了更改,以便在直接运行测试时保持安全,而不涉及 TestExecutionContext。 如果您将异常设置为内部,那么您就打破了它。 事实上,我很高兴打破它,但团队同意我们不应该,所以我修复了它。 你只需要决定你愿意打破什么。 该修复没有 __not__ 处理当前问题,它处理在测试本身内部捕获异常,而不是在调用方法的代码中。

在测试中捕获 AssertionException 是我所说的经典“气味”。 这可能是错误的,但您必须查看具体案例才能知道。 我会告诉用户“捕获 AssertionException 很可能是一个错误,除非您对 NUnit 内部结构有详细的了解。为了确保正确的行为,您不应该尝试更改测试的结果,并且应该在执行任何您想做的事情后重新抛出异常用它。” 但是,我仍然认为这些信息不属于 XML 文档或一般用户文档。 我会把它作为内部部分的一个页面。

顺便说一句,我过去的经验是记录某些东西然后告诉人们不要使用它会导致使用量增加。 😄

好点,谢谢! 我想我部分考虑的是 CYA。 还有另一个要素,那就是作为库的使用者,我个人很高兴被告知每一个重大更改,这样我就可以在自己的代码中寻找错误的假设。 特别是对于编译器没有注意到的此类更改。 因为我会很感激,所以我在某种程度上将其投射到我们的用户身上。

我们应该开始一个新问题来跟踪文档修复吗?

为什么是新的?

标题看起来像是我们将作为一个已回答的问题而不是修复的问题。 我们通常会更改标题并使用此问题来跟踪文档更改吗?

我通常所做的是审查问题并将其分类......在这种情况下作为文档问题。 诚然,我们已经花了很长时间首先将其作为可能的错误进行讨论。 这是一个项目管理问题,真的,超出了我的范围。

我的意思是,做一些合乎逻辑和一致的事情。 😄 关闭它不是一个错误是一种选择。 然而,这将投诉与解决方案分开。 当您将其重新归类为文档错误并可能编辑标题时,编写解释性评论可以使解决方案与投诉保持一致。 在任何情况下,完成问题的问题标题最终都会出现在发行说明中,因此它应该能够表达所发生的事情。

您能否提供一个示例,说明您在捕获异常时实际尝试执行的操作? 我很确定我们可以帮助您找到更好的解决方案。

我目前面临同样的问题,因为我的测试在升级到 3.10 后开始失败。 现在这是我的问题:
我有自定义测试断言方法。 那些内部调用 NUnit.Assert。 我测试这些断言方法以确保它们在我预计它们会失败时确实会失败。
目前我的测试捕获AssertionException以验证自定义断言方法将导致测试失败,但升级后所有这些测试由于上述原因开始失败。

测试 3.10 的推荐方法是什么?

谢谢。

最简单的方法是使用 Assert.Throws 来捕获异常而不是自己做。 Assert.Throws 了解 NUnit 内部结构并确保不报告失败。

前几天我在我的通知中看到了这一点,并没有多想。 但是,更新我的一个库项目,它在其逻辑中执行“Assert.That”(它是一个测试辅助框架——专门用于在手动编码 EF 上下文和实体并编码相应的数据库时提供一种简单的方法来测试 EF 持久性手动迁移(或使用 FluentMigrator 之类的东西)——所以,换句话说,它应该代表调用者执行断言),我现在让测试在不应该失败的地方失败——特别是一个测试是断言某个条件应该抛出测试失败——它确实如此,但由于“捕获”不再像往常一样有效,这些测试虽然实际通过,但被报告为失败。

所以我想我不能抛出正确的 NUnit 断言,或者在库代码中使用Assert.That旨在通过为用户执行繁琐的任务来帮助更快的测试开发?

我同意先前的说法,即如果用户不能使用异常,则用户不应抛出异常; 然而,这并不能解决我为消费者准备了Assert.That代码的问题,并测试证明他们确实抛出失败,因为代码做了它应该做的事情:nunit 断言失败。

也许我们应该将一些结果 API 产品化?

@fluffynuts如果我理解正确, catch子句在您的测试代码中,而不是您的用户库的代码中。 (如果它也在用户库中,我们可以讨论,但问题略有不同。)

当然,NUnit 对自身进行了测试,因此在进行此更改时,我们遇到了完全相同的潜在问题。 当然,我们修改了自己的测试,以便它们继续通过。 你可以做同样的事情。

第一道防线是Assert.Throws 。 在大多数测试失败行为的情况下,您可以直接将try / catch(AssertionException)替换Assert.Throws<AssertionException> 。 在内部Assert.Throws会创建一个一次性隔离的TestExecutionContext因此您的实际测试结果不会被您正在测试的错误所污染。

在少数情况下,您可能需要创建自己的隔离上下文。 您可以通过检查Assert.Throws的代码来了解如何做到这一点。

然而,NUnit 自己的大部分故障测试实际上使用了不同的方法。 我们有一个单独的程序集,其中包含由我们自己的测试程序运行的测试。 我们捕获结果,然后可以检查它。 这是更多的工作,但避免将太多关于 NUnit 的实现细节放入我们的测试中。 大多数执行此操作的方法都可以在我们的测试实用程序程序集中找到。 我认为这就是@jnm2所指的。

发布您的任何问题或离线联系我以获得更多帮助。

谢谢(你的)信息。 是的,断言是在测试代码中捕获的。 Assert.Throws 通常可以解决问题,但有问题的特定测试运行库代码时,允许测试应放入并从数据库中检索的日期时间值的“太低”增量。 大多数情况下,为此到 localdb 的漂移是 1 毫秒,但有时它漂移得更高——这个测试只是为了断言“有时”发生。 因此,目前,我在 4 个并行线程中运行代码 10 次,预计至少会失败一次。 不幸的是,对于这种情况,Assert.Throws 过于确定,所以我要么需要停止使用 nunit 异常(或 Assert,在库代码中),要么我需要学习你们使用的 fu。 我很感激任何指示 - 如果您愿意,可以离线进行。

嘿,我刚刚意识到我一直盯着的 AsyncVoidVerificationScope 的消息泵测试失败与 @fluffynuts 遇到的问题完全相同。 实际上由于 NUnit 3.10 升级,而不是我的 [assembly: RestoreSynchronizationContext] ITestAction。 在同时做多件事时忽略它们,这对我来说是正确的。 :D

无论如何,看起来我想要做的是像Assert.Throws一样将我的委托调用包装在using (new TestExecutionContext.IsolatedContext()) Assert.Throws中。

Assert.ThrowsAssert.ThrowsAsync在调用异步委托之前不会建立隔离的上下文。 为什么是这样?

@fluffynuts很久以前,当我第一次写Assert.Catch它只是捕获了任何异常,如果没有抛出异常也不会产生错误。 不幸的是,情况发生了变化,我们不再有Assert.Throws的“中立”等价物。 不过,你可以写一个。 这是基于Assert.Throws的代码的未经测试的版本。 它只处理同步代码,但您可以使用Assert.Throws附加代码轻松地为异步扩展它。

```C#
公共静态无效异常 SafelyCatchAnNUnitException(TestDelegate 代码)
{
异常被捕获Exception = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
``

该方法将返回抛出的任何异常,清除 NUnit 的 assert 方法留下的错误的任何残余。 如果没有抛出异常,则返回 null。

还可以在代码中的更高级别使用IsolatedContext()以从中检索实际测试结果。 这将允许您完全在测试结果级别进行测试,而无需提及任何异常。 我已经考虑过为这种类型的测试编写一个库,我认为这会优于 NUnit 目前进行自己测试的方式。

此页面是否有帮助?
0 / 5 - 0 等级