【问题标题】:Unit test expected to throw an exception when calling an asynchronous operation on MSTest在 MSTest 上调用异步操作时,单元测试预计会引发异常
【发布时间】:2011-10-01 01:15:28
【问题描述】:

下面的源代码是关于我的问题的示例代码 sn-p。我希望调用异步操作时会发生异常。

单元测试

[TestMethod()]
[ExpectedException(typeof(Exception))]
public void OperateAsyncTest()
{
    //Arrange
    var testAsyncClass = new TestAsyncClass();
    //Act
    testAsyncClass.OperateAsync();
}

代码

public class TestAsyncClass
{
    public void OperateAsync()
    {
        ThreadPool.QueueUserWorkItem( obj =>{
            throw new Exception("an exception is occurred.");
        });
    }
}

但是,MsTest 无法捕获异常,因为可能测试线程与抛出异常的线程不同。 如何解决这个问题?有什么想法吗?

以下代码是我的解决方法,但它既不聪明也不优雅。

解决方法

[TestClass()]
public class TestAsyncClassTest
{
    private static Exception _exception = new Exception();
    private static readonly EventWaitHandle ExceptionWaitHandle = new AutoResetEvent(false);

    static TestAsyncClassTest()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        _exception = (Exception)e.ExceptionObject;
        ExceptionWaitHandle.Set();
    }


    [TestMethod()]
    [ExpectedException(typeof(Exception))]
    public void OperateAsyncTest()
    {
        //Arrange
        var testAsyncClass = new TestAsyncClass();
        //Act
        lock(_exception)
        {
            testAsyncClass.OperateAsync();
            ExceptionWaitHandle.WaitOne();
            throw _exception;
        }
    }
}

【问题讨论】:

    标签: c# unit-testing mstest


    【解决方案1】:

    也许您可以将异步操作实现为Task,从OperateAsync 返回,然后从调用方返回Task.Wait

    Task.Wait 将“观察”异常,以便您的单元测试可以检测到它(前提是您使用 ExpectedException 属性装饰它)。

    代码如下所示:

    public class TestAsyncClass {
    
        public Task OperateAsync() {
            return Task.Factory.StartNew(
                () => {
                    throw new Exception("an exception is occurred.");
                }
            );
        }
    
    }
    
    [TestClass]
    public class TestAsyncClassTest {
    
        [TestMethod]
        [ExpectedException(typeof(AggregateException))]
        public void OperateAsyncTest() {
            var testAsyncClass = new TestAsyncClass();
            testAsyncClass.OperateAsync().Wait();
        }
    
    }
    

    请注意,您将从Task.Wait 获得AggregateException。它的InnerException 将是您从OperateAsync 抛出的异常。

    【讨论】:

    • +1:是的,没错。但是,如果我使用Task而不是Thread,这个问题就解决了,但是当Task进程中发生未处理的异常并且主线程不等待任务完成时,Task不能立即抛出未处理的异常。此外,由于异步测试而使用 Task 似乎是不合理的。如果我有误解,请告诉我。谢谢你的回答。
    • 从一个线程抛出的异常(无论是否由Task 包裹)永远不会被另一个线程直接捕获。原因很简单:异常机制依赖于栈展开,不同的线程有不同的栈。所以,无论你怎么写,你都必须以某种方式在“源”线程中“打包”异常,并在“目标”中“解包”它。而且由于我们在这里讨论的是两个不同的线程,因此您必须通过某种同步机制(即某种“等待”)。那么,为什么不使用已经在Task 中实现的人们比你我聪明得多的机制呢?
    • 顺便说一句,您认为“因为异步测试而使用 Task 似乎不合理”有什么具体原因吗?
    • 我的想法是我可以使用UnhandledException 事件在其他thead(非主)抛出异常时记录或执行某些操作。但是,如果我使用 Task 并且想要接收任务的异常,则需要等待任务完成,不是吗?因此,我想使用 Thread 而不是 Task,尽管可能有一个关于主线程和任何任务同步的解决方案,即在任务抛出异常时调用 SynchronizationContext.Send。
    • 什么时候你想登录?如果您想立即记录,为什么要使用异常(至少,为什么不在它们的原始线程中捕获它们)?另外,我不明白为什么您认为线程会比Task 给您带来任何优势。毕竟,Task 包装了一个线程。
    【解决方案2】:

    异常发生在不同的线程上,必须进行相应的处理。有几个选项。请看这两个帖子:

    【讨论】:

      猜你喜欢
      • 2012-05-20
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 2017-10-14
      • 2014-02-27
      • 1970-01-01
      相关资源
      最近更新 更多