【问题标题】:Unit testing async method for specific exception针对特定异常的单元测试异步方法
【发布时间】:2012-10-01 23:22:41
【问题描述】:

有没有人举例说明如何在 Windows 8 Metro 应用程序中对异步方法进行单元测试,以确保它抛出所需的异常?

给定一个带有异步方法的类

public static class AsyncMathsStatic
{
    private const int DELAY = 500;

    public static async Task<int> Divide(int A, int B)
    {
        await Task.Delay(DELAY);
        if (B == 0)
            throw new DivideByZeroException();
        else
            return A / B;
    }
}

我想使用新的 Async.ExpectsException 构造编写一个测试方法。我试过了:-

[TestMethod]
public void DivideTest1()
{
    Assert.ThrowsException<DivideByZeroException>(async () => { int Result = await AsyncMathsStatic.Divide(4, 0); });
}

当然,测试不会等待 async 方法完成,因此会导致未引发异常的测试失败。

【问题讨论】:

    标签: c# unit-testing async-await


    【解决方案1】:

    您可以将async Task 单元测试与常规ExpectedExceptionAttribute 一起使用:

    [TestMethod]
    [ExpectedException(typeof(DivideByZeroException))]
    public async Task DivideTest1()
    {
      int Result = await AsyncMathsStatic.Divide(4, 0);
    }
    

    评论更新: ExpectedExceptionAttribute 在 Win8 单元测试项目 has been replacedAssert.ThrowsException 上,这是很好的无证 AFAICT。这是a good change design-wise,但我不知道为什么它在Win8上支持。

    好吧,假设没有 async-compatible Assert.ThrowsException (由于缺乏文档,我无法确定是否有),您可以自己构建一个:

    public static class AssertEx
    {
      public async Task ThrowsExceptionAsync<TException>(Func<Task> code)
      {
        try
        {
          await code();
        }
        catch (Exception ex)
        {
          if (ex.GetType() == typeof(TException))
            return;
          throw new AssertFailedException("Incorrect type; expected ... got ...", ex);
        }
    
        throw new AssertFailedException("Did not see expected exception ...");
      }
    }
    

    然后这样使用它:

    [TestMethod]
    public async Task DivideTest1()
    {
      await AssertEx.ThrowsException<DivideByZeroException>(async () => { 
          int Result = await AsyncMathsStatic.Divide(4, 0);
      });
    }
    

    请注意,这里的示例只是对异常类型进行精确检查;您可能更喜欢允许后代类型。

    2012-11-29 更新:打开 UserVoice suggestion 以将其添加到 Visual Studio。

    【讨论】:

    • 这在其他版本的 C# 中运行良好,但已从 Windows 8 Metro 应用程序中删除 blogs.msdn.com/b/visualstudioalm/archive/2012/06/18/…
    • 嗯,这很“有趣”!我已经更新了我的答案,以展示如果 Win8 单元测试不支持它,您如何实现async-兼容ThrowsException
    • 感谢 Stephen 确实有效,尽管我不敢相信看起来如此优雅的新功能需要这样的 hack,尤其是在 Windows 8 Metro 如此强调异步方法的情况下。希望 Windows 8 发布之后的最终文档会更及时。
    【解决方案2】:

    几天前我遇到了类似的问题,最终创建了类似于斯蒂芬上面的答案的东西。它以Gist 的形式提供。希望对您有所帮助 - github gist 提供了完整的代码和示例用法。

    /// <summary>
    /// Async Asserts use with Microsoft.VisualStudio.TestPlatform.UnitTestFramework
    /// </summary>
    public static class AsyncAsserts
    {
        /// <summary>
        /// Verifies that an exception of type <typeparamref name="T"/> is thrown when async<paramref name="func"/> is executed.
        /// The assertion fails if no exception is thrown
        /// </summary>
        /// <typeparam name="T">The generic exception which is expected to be thrown</typeparam>
        /// <param name="func">The async Func which is expected to throw an exception</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public static async Task<T> ThrowsException<T>(Func<Task> func) where T : Exception
        {
            return await ThrowsException<T>(func, null);
        }
    
        /// <summary>
        /// Verifies that an exception of type <typeparamref name="T"/> is thrown when async<paramref name="func"/> is executed.
        /// The assertion fails if no exception is thrown
        /// </summary>
        /// <typeparam name="T">The generic exception which is expected to be thrown</typeparam>
        /// <param name="func">The async Func which is expected to throw an exception</param>
        /// <param name="message">A message to display if the assertion fails. This message can be seen in the unit test results.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public static async Task<T> ThrowsException<T>(Func<Task> func, string message) where T : Exception
        {
            if (func == null)
            {
                throw new ArgumentNullException("func");
            }
    
            string failureMessage;
            try
            {
                await func();
            }
            catch (Exception exception)
            {
                if (!typeof(T).Equals(exception.GetType()))
                {
                    // "Threw exception {2}, but exception {1} was expected. {0}\nException Message: {3}\nStack Trace: {4}"
                    failureMessage = string.Format(
                        CultureInfo.CurrentCulture,
                        FrameworkMessages.WrongExceptionThrown,
                        message ?? string.Empty,
                        typeof(T),
                        exception.GetType().Name,
                        exception.Message,
                        exception.StackTrace);
    
                    Fail(failureMessage);
                }
                else
                {
                    return (T)exception;
                }
            }
    
            // "No exception thrown. {1} exception was expected. {0}"
            failureMessage = string.Format(
                        CultureInfo.CurrentCulture,
                        FrameworkMessages.NoExceptionThrown,
                        message ?? string.Empty,
                        typeof(T));
    
            Fail(failureMessage);
            return default(T);
        }
    
        private static void Fail(string message, [CallerMemberName] string assertionName = null)
        {
            string failureMessage = string.Format(
                CultureInfo.CurrentCulture,
                FrameworkMessages.AssertionFailed,
                assertionName,
                message);
    
            throw new AssertFailedException(failureMessage);
        }
    }
    

    【讨论】:

      【解决方案3】:

      been added in Visual Studio 2012 Update 2 开始支持在 ThrowsException 方法中使用异步 lambda,但仅适用于 Windows 应用商店测试项目。

      一个问题是您需要使用Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.Assert 类来调用ThrowsException

      因此,要使用新的 ThrowsException 方法,您可以执行以下操作:

      using AsyncAssert = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.Assert;
      
      [TestMethod]
      public void DivideTest1()
      {
          AsyncAssert.ThrowsException<DivideByZeroException>(async () => { 
              int Result = await AsyncMathsStatic.Divide(4, 0); });
      }
      

      【讨论】:

        【解决方案4】:
        [TestMethod]
        public void DivideTest1()
        {
            Func<Task> action = async () => { int Result = await AsyncMathsStatic.Divide(4, 0); });
            action.ShouldThrow<DivideByZeroException>();
        }
        

        使用 FluentAssertions nuget 包中的 .ShouldThrow() 对我有用

        【讨论】:

          【解决方案5】:

          随着ThrowsExceptionAsync method 的添加,现在可以在本机覆盖,无需第三方或 MSTest 中的扩展方法:

          await Assert.ThrowsExceptionAsync<Exception>(() => { Fail(); });
          

          【讨论】:

            【解决方案6】:

            这对我有用

                public async Task TestMachineAuthBadJson() {
                    // Arrange
            
                    // act
                    DocsException ex = await Assert.ThrowsExceptionAsync<DocsException>(() => MachineAuth.GetToken());
                    //assert
                    StringAssert.Contains(ex.Message, "DOCS-API error: ");
            
                    }
            

            【讨论】:

              【解决方案7】:

              这是一个老问题,但我现在偶然发现了这个问题,并决定对这个问题给出更新的答案。

              Xuint 现在支持使用 Assert.ThrowsAsync 方法进行异步异常测试。

              【讨论】:

              • 值得一提的是,应该拭目以待。例如:等待 Assert.ThrowsAsync...
              猜你喜欢
              • 1970-01-01
              • 2020-07-14
              • 2011-11-07
              • 2017-08-05
              • 1970-01-01
              • 2018-04-09
              • 2018-12-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多