【问题标题】:Unit testing asynchronous code [closed]单元测试异步代码[关闭]
【发布时间】:2016-02-23 19:25:43
【问题描述】:

我有一个简单的类,方法如下:

partial class SimpleClass
{
    private readonly ISimpleManager _simpleManager;
    public SimpleClass(ISimpleManager simpleManager)
    {
        _simpleManager = simpleManager;
    }

    public async void SimpleMethod()
    {
        IsInProgress = true;
        DoSomeWork();
        Task<int> hardWork0Task = _simpleManager.DoHardWork0Async();
        Task<int> hardWork1Task = _simpleManager.DoHardWork1Async();
        DoIndependentWork();
        int hardWork0Result = await hardWork0Task.ConfigureAwait(false);
        DoDependentWork(hardWork0Result);
        int hardWork1Result = await hardWork1Task.ConfigureAwait(false);
        DoDependentWork(hardWork1Result);
        IsInProgress = false;
    }
}

假设属性IsInProgress 只是bool 属性通知GUI 其状态以允许刷新进度条。 DoSomeWorkDoDependentWorkDoIndependentWork 是一些使用或不使用努力工作的结果的方法。

ISimpleManager 是在这种情况下你能想象到的最简单的界面:

interface ISimpleManager
{
    Task<int> DoHardWork0Async();
    Task<int> DoHardWork1Async();
}

我必须使用 Moq 和 NUnit 编写一些单元测试。我该如何为这种情况编写单元测试?我想检查在与 GUI 异步运行的整个代码期间,IsInProgress 属性的状态是否未更改为 false。是否有意义?可能吗?如果我的async 方法返回Task 或通用Task&lt;T&gt; 怎么办?如果我为true 配置等待会怎样?

【问题讨论】:

  • 您可以使用存根使代码运行很长时间,然后您就可以捕获正在进行的状态。
  • 拥有返回 void 的异步方法是个坏主意。我建议删除IsInProgress 属性并返回Task
  • @Sergey Berezovskiy,好的,正如我所提到的,我不介意使用Task 或通用Task&lt;T&gt; 而不是void。您能否向我展示仅针对 Task 返回方法的完整解决方案?这对我会有帮助。我还有其他问题。如果您正在为 Caliburn 视图模型开发测试,您有一些方法必须是 async void,即绑定到按钮单击事件的操作。在这种情况下该怎么办?公开显示返回 Task 的方法,除了返回 async void 的方法?这对我来说听起来不太好,因为您正在将 sut 代码更改为可测试。
  • @Ilya Kogan,这是我很久以来的想法,但我的队友拒绝了。通过使用锁或类似的东西来管理它是最好的解决方案,因为它不会不必要地延迟测试处理时间。但是你可以把你的解决方案放在答案中。这可能是最好的。
  • 为什么对我的问题投反对票?

标签: c# .net unit-testing asynchronous moq


【解决方案1】:

您只需要控制您的ISimpleManager 任务何时完成。根据 Ilya 的回答,您可以使用 SemaphoreSlim 执行此操作,也可以直接使用 TaskCompletionSource&lt;T&gt;。我一般更喜欢TaskCompletionSource&lt;T&gt;,因为它更简单;但是,SemaphoreSlim 实例可以重复使用,而TaskCompletionSource&lt;T&gt; 只能触发一次。

附带说明,您应该避免使用async void,除非该方法是事件处理程序。它应该是任何类型的“默认值”——默认值应该是返回Task,除非你绝对不能。所以在这个例子中,SimpleMethod 当然应该返回Task

这是使用 TCS 的样子:

async Task MyTestMethod()
{
  // Set up the mock object
  var tcs0 = new TaskCompletionSource<int>();
  var tcs1 = new TaskCompletionSource<int>();
  var stub = new Mock<ISimpleManager>();
  stub.Setup(x => x.DoHardWork0Async()).Returns(tcs0.Task);
  stub.Setup(x => x.DoHardWork1Async()).Returns(tcs1.Task);

  var sut = new SimpleClass(stub.Object);
  var task = sut.SimpleMethod();

  Assert.True(sut.InProgress);
  tcs0.SetResult(7);
  Assert.True(sut.InProgress);
  tcs1.SetResult(13);
  await task;
  Assert.False(sut.InProgress);
}

【讨论】:

  • 按预期工作。即使是async void签名(当然不包括关键字asyncawait在测试中)。
【解决方案2】:

您可以通过以下方式对 IsInProgress 设置是否正确进行单元测试:

  1. 创建一个等待信号量:

    var sem = new SemaphoreSlim(1);
    
  2. 使用以下实现创建存根ISimpleManager

    async Task<int> DoHardWork0Async() {
       await sem.WaitAsync();
       return 0;
    }
    
  3. 致电SimpleMethod。它将卡在对DoHardWork0Async的调用中的信号量上

  4. 验证IsInProgress的效果

  5. 作为清理步骤,释放信号量:

    sem.Release();
    

【讨论】:

    猜你喜欢
    • 2014-09-29
    • 2010-09-09
    • 1970-01-01
    • 2020-04-15
    • 2019-11-12
    • 1970-01-01
    • 2012-07-15
    • 2010-12-05
    • 2014-03-13
    相关资源
    最近更新 更多