2011-04-20 69 views
1

我的团队在C#项目中广泛使用NUnit单元测试。最近我们已经开始在.NET 4中使用任务并行库(TPL),它已经为我们引入了一个皱纹。如何检测nunit测试套件中的UnobservedTaskException错误

在TPL中,如果某个任务出现故障(即执行该任务时引发了异常),则该任务的Exception属性必须至少检索一次。如果不是,当Task对象由垃圾回收器完成时,会抛出异常,终止进程。

可以通过注册TaskScheduler.UnobservedTaskException的处理程序来检测和防止这种情况。我们已经为我们的一些测试用例重现了未观察到的任务异常错误,但我宁愿有一些方法来修改NUnit运行测试的方式,以便为每个测试注册一个UnobservedTaskException处理程序,然后在测试之后垃圾收集被迫清除任何没有被观察到的异常的任务。那么我会喜欢这样做,导致测试失败。

其他团队如何解决这个问题?你如何检测通过的测试用例(即完成没有任何异常),但留下一个或多个任务对象没有被观察到的异常?

回答

2

这是我如何做到这一点:

[Test] 
public void ObservesTaskException() 
{ 
    bool wasUnobservedException = false; 

    TaskScheduler.UnobservedTaskException += 
     (s, args) => wasUnobservedException = true; 

    CauseATaskToThrowInTheSystemUnderTest(); 

    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 

    Assert.That(wasUnobservedException, Is.False); 
} 

到CauseATaskToThrowInTheSystemUnderTest()的调用是任何你需要做的占位符。我建议将代码包装在这样的函数中,因为它有助于确保在GC运行时引发异常的Task对象无法访问。如果任务对象可达,终结器将不会运行,并且此测试不会测试任何内容。

显然,在垃圾收集发生之前确保任务已完成也很重要。你如何做到这一点(如果你可以的话)取决于你的特定代码。也许你的程序流程确保了这种情况。如果不是这样,如果你的测试可以访问任务的对象,你可以做到以下几点:

var continuation = GetTheTaskFromTheSystemUnderTest(); 
    .ContinueWith(t => {}); 

CauseATaskToThrowInTheSystemUnderTest(); 

bool isTaskCompleted = continuation.Wait(SomeSuitableTimeout); 

您应该添加isTaskCompleted把断言。

超时时间是为了防止将来代码被破坏 - 您不希望测试挂起。该值应该非常小。如果您发现实际上需要等待很长时间才能完成任务,那么您的测试套件对于频繁使用可能会太慢。

有权访问您创建的任务(包括您在其他任务上创建的延续任务)是设计可测试性时需要考虑的一个注意事项。这样做时,通常的折衷适用。我尝试在我的代码创建任务时包含这样的测试 - 这是我的代码的一个重要行为,需要进行测试。

+0

我最终自己也得到了同样的答案。很棒。在调用GC.Collect和GC.WaitForPendingFinalizers之前,有一些可能会完成任务,但在实践中,我发现可能性太低以至于无所谓。 – anelson 2011-08-06 04:10:54

+0

你确定这可以吗?我只是尝试了'Task.Run(Throw)'和'Task.Factory.StartNew(Throw)'在一个'int Throw(){throw new Exception(); },在任务上尝试了一个'use()'范围,尝试了连续任务,并且根本没有任何任务变量。即使尝试使用Thread.Sleep()或使用ManualResetEvent确保任务完成并给系统一些时间来完成任务,该事件也不会引发给我。 – angularsen 2016-02-02 12:23:38

+0

重复问题: http://stackoverflow.com/questions/21266137/test-for-unobserved-exceptions – angularsen 2016-02-02 12:30:54

0

我会保持单元测试的确定性,然后用CHESS或Jinx做功能测试。

这意味着我没有代码依赖于单元测试逻辑中的并发流。至于测试多个异常打包/解包,我会测试它,就好像它是一个循环:没有例外,一个例外和两个例外证明整个事情。

+0

够公平的;你如何解决CHESS或Jinx中未被发现的例外问题? – anelson 2011-04-21 23:22:43

+0

看来你正在测试TPL的核心功能。也就是说,你正在检查它是否仍然组装一些未处理的异常,并将它们传递给正确的启动线程。您似乎很清楚每个线程上抛出的异常。我会单元测试线程上的逻辑,并依靠dotNET仍然适用于线程的事实。 – GregC 2011-04-22 03:53:11

+0

断言一段代码抛出Assert.Throws ()的某个异常。 CHESS和Jinx可以以各种交错方式运行您的代码,并查看您的单元测试是否仍然存在,确保共享状态得到适当保护。 – GregC 2011-04-22 03:55:37