代码之家  ›  专栏  ›  技术社区  ›  ChrisW

MVC2异步控制器的构建单元测试

  •  10
  • ChrisW  · 技术社区  · 14 年前

    我正在考虑将一些MVC控制器重写为异步控制器。我已经对这些控制器进行了单元测试,但是我正在尝试理解如何在异步控制器环境中维护它们。

    例如,目前我有这样一个动作:

    public ContentResult Transaction()
    {
        do stuff...
        return Content("result");
    }
    

    我的单元测试基本上是这样的:

    var result = controller.Transaction();
    Assert.AreEqual("result", result.Content);
    

    好吧,这很简单。

    但是当你的控制器变成这样时:

    public void TransactionAsync()
    {
        do stuff...
        AsyncManager.Parameters["result"] = "result";
    }
    
    public ContentResult TransactionCompleted(string result)
    {
        return Content(result);
    }
    

    您认为应该如何构建单元测试?当然,您可以在测试方法中调用async initiator方法,但是如何获取返回值呢?

    谢谢你的建议。

    2 回复  |  直到 14 年前
        1
  •  18
  •   Matthew Abbott    14 年前

    与任何异步代码一样,单元测试需要知道线程信号。NET包含一个名为AutoResetEvent的类型,它可以阻止测试线程,直到异步操作完成:

    public class MyAsyncController : Controller
    {
      public void TransactionAsync()
      {
        AsyncManager.Parameters["result"] = "result";
      }
    
      public ContentResult TransactionCompleted(string result)
      {
        return Content(result);
      }
    }
    
    [TestFixture]
    public class MyAsyncControllerTests
    {
      #region Fields
      private AutoResetEvent trigger;
      private MyAsyncController controller;
      #endregion
    
      #region Tests
      [Test]
      public void TestTransactionAsync()
      {
        controller = new MyAsyncController();
        trigger = new AutoResetEvent(false);
    
        // When the async manager has finished processing an async operation, trigger our AutoResetEvent to proceed.
        controller.AsyncManager.Finished += (sender, ev) => trigger.Set();
    
        controller.TransactionAsync();
        trigger.WaitOne()
    
        // Continue with asserts
      }
      #endregion
    }
    

        2
  •  1
  •   Dawid Kowalski    12 年前

    我编写了简短的异步控制器扩展方法,简化了单元测试。

    static class AsyncControllerExtensions
    {
        public static void ExecuteAsync(this AsyncController asyncController, Action actionAsync, Action actionCompleted)
        {
            var trigger = new AutoResetEvent(false);
            asyncController.AsyncManager.Finished += (sender, ev) =>
            {
                actionCompleted();
                trigger.Set();
            };
            actionAsync();
            trigger.WaitOne();
        }
    }
    

    这样我们就可以简单地隐藏线程“噪音”:

    public class SampleAsyncController : AsyncController
    {
        public void SquareOfAsync(int number)
        {
            AsyncManager.OutstandingOperations.Increment();
    
            // here goes asynchronous operation
            new Thread(() =>
            {
                Thread.Sleep(100);
    
                // do some async long operation like ... 
                // calculate square number
                AsyncManager.Parameters["result"] = number * number;
    
                // decrementing OutstandingOperations to value 0 
                // will execute Finished EventHandler on AsyncManager
                AsyncManager.OutstandingOperations.Decrement();
            }).Start();
        }
    
        public JsonResult SquareOfCompleted(int result)
        {
            return Json(result);
        }
    }
    
    [TestFixture]
    public class SampleAsyncControllerTests
    {
        [Test]
        public void When_calling_square_of_it_should_return_square_number_of_input()
        {
            var controller = new SampleAsyncController();
            var result = new JsonResult();
            const int number = 5;
    
            controller.ExecuteAsync(() => controller.SquareOfAsync(number),
                                    () => result = controller.SquareOfCompleted((int)controller.AsyncManager.Parameters["result"]));
    
            Assert.AreEqual((int)(result.Data), number * number);
        }
    }
    

    如果你想知道更多,我写了一篇关于如何 Unit test ASP.NET MVC 3 asynchronous controllers using Machine.Specifications 或者如果你想检查这个代码的话 github