【问题标题】:Async and await - unit testing issues异步和等待 - 单元测试问题
【发布时间】:2015-10-16 22:50:42
【问题描述】:

我有一个异步函数说 DoWork() 它做一些工作并将结果存储在一个文件中。所以返回类型是无效的。我需要为此功能编写一个单元测试,这就是我的问题。

internal sealed class MainClass: InheritedClass
 {
   public override async void Start()
     {
        base.Start();
        var result = await TaskEx.Run(() => DoSomeWork());
        Store(result);
     }
}

在单元测试类中,如果我有类似下面的代码,它不会等待,它会移动到下一行执行,但测试失败。那么,我怎样才能让它等待,直到基本操作完成。

public async Task StoreDataTest()
{
   var t = Task.Factory.StartNew(MainClass.Start);
   t.Wait();
   var data = UtilityClass.GetStoredData();
   Assert.IsNotNull(data, "No data found");
}

重写 Start() 时,我无法更改返回类型。在我的情况下,基类在许多地方被继承,并且在基类中更改虚拟方法的返回类型不是一种选择。那么我应该开始使用后台工作者和事件处理程序吗?我应该使用这种方法吗?

我们不能使用 .NET 4.5,因为我们仍然支持 XP :( 所以,我在 .net 4.0 上使用 BCL 库来获得异步和等待功能。最近,我看到一些博客建议不要使用 backgroundworker,因为它在以后的 .NET 版本中过时了?

List<EntityClass> result = null;
            var worker = new BackgroundWorker();
            worker.DoWork += (sender, e) =>
            {
                result = GetData();
            };
            worker.RunWorkerCompleted += (sender, eventArgs) =>
            {
                if (result == null)
                {
                    return;
                }
                Store(result);

                // Event used for Unit testing purpose
                if (DataStoredEvent != null)
                {
                    DataStoredEvent(this, new EventArgs());
                }
            };
            worker.RunWorkerAsync();

谢谢

【问题讨论】:

  • 我明白你的问题 - 你有一个 async void 方法 :) 只要确保该方法返回 Task,你会没事的(它给你一些东西到 await/@987654328 @)。
  • Avoid async void,你没有办法等待它完成。
  • I have a async function say DoWork() which does some work and stores the result in a file. So the return type is void. 后者实际上并非源自前者。您有一个函数可以完成某些工作并将结果存储在文件中这一事实意味着返回类型应该void,除非它是一个同步函数。
  • 重写 Start() 时无法更改返回类型。在我的情况下,基类在许多地方被继承,并且在基类中更改虚拟方法的返回类型不是一种选择。那么我应该开始使用后台工作者和事件处理程序吗?

标签: c# .net unit-testing asynchronous async-await


【解决方案1】:

正如其他人所指出的,最好的解决方案是将您的方法更改为返回 Task,这是没有返回值的异步方法的自然返回类型:

internal sealed class MainClass: InheritedClass
{
  public override async Task StartAsync()
  {
    base.Start();
    var result = await TaskEx.Run(() => DoSomeWork());
    Store(result);
  }
}

附带说明,我建议您不要在业务逻辑类中使用TaskEx.Run;该方法最好只从 UI 层调用。

一旦您的方法正确返回 Task,单元测试就很简单了:

public async Task StoreDataTest()
{
  await MainClass.StartAsync();
  var data = UtilityClass.GetStoredData();
  Assert.IsNotNull(data, "No data found");
}

基于 cmets 的更新:

更改基类是最好的解决方案。它是 hard 解决方案的事实并不会改变它是 最佳 解决方案的事实。

也就是说,如果您不想实现最佳解决方案,可能使用AsyncContext type from my AsyncEx libraryasync void 方法进行单元测试:

public void StoreDataTest()
{
  AsyncContext.Run(() => MainClass.Start());
  var data = UtilityClass.GetStoredData();
  Assert.IsNotNull(data, "No data found");
}

请注意,单元测试方法实际上是同步的,因为它会阻塞单元测试线程,直到async void 方法完成。这是一种危险的做法,不建议用于实际的应用程序代码,但对单元测试很有效(换句话说,只在入口点执行此操作)。

【讨论】:

  • 不幸的是,我在覆盖 Start() 时无法更改返回类型?在我的情况下,基类在许多地方被继承,并且在基类中更改虚拟方法的返回类型不是一个选项...
猜你喜欢
  • 1970-01-01
  • 2017-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
相关资源
最近更新 更多