【问题标题】:C# Unit Test - Prove that async methods in a Task.WhenAll() run asynchronously properlyC# 单元测试 - 证明 Task.WhenAll() 中的异步方法正确异步运行
【发布时间】:2021-12-29 22:31:18
【问题描述】:

我有一个我想测试的方法,它有四个异步运行的方法。我想要一个单元测试来证明它们是并行运行的。我不知道该怎么做,也无法在网上找到任何东西。

这是我所拥有的一个例子

我正在尝试测试的方法

public async Task<IEnumerable<ReturnData>> GetData()
{
    List<ReturnData> returnData = new List<ReturnData>();

    Task<IEnumerable<ReturnData>> dataTypeA = dataServiceA.GetDataTypeA();
    Task<IEnumerable<ReturnData>> dataTypeB = dataServiceB.GetDataTypeB();
    Task<IEnumerable<ReturnData>> dataTypeC = dataServiceC.GetDataTypeC();
    Task<IEnumerable<ReturnData>> dataTypeD = dataServiceD.GetDataTypeD();

    await Task.WhenAll(dataTypeA, dataTypeB, dataTypeC, dataTypeD);

    returnData.AddRange(dataTypeA.Result);
    returnData.AddRange(dataTypeB.Result);
    returnData.AddRange(dataTypeC.Result);
    returnData.AddRange(dataTypeD.Result);

    return returnData;
}

我的单元测试

[TestMethod]
public async Task GetDataTestAsync()
{

    mockedDataServiceA.Setup(a => a.GetDataTypeA())
        .Callback(() => Thread.Sleep(1000))
        .ReturnsAsync(mockedDataA);

    mockedDataServiceB.Setup(a => a.GetDataTypeB())
        .Callback(() => Thread.Sleep(1000))
        .ReturnsAsync(mockedDataB);

    mockedDataServiceC.Setup(a => a.GetDataTypeC())
        .Callback(() => Thread.Sleep(1000))
        .ReturnsAsync(mockedDataC);

    mockedDataServiceD.Setup(a => a.GetDataTypeD())
        .Callback(() => Thread.Sleep(1000))
        .ReturnsAsync(mockedDataD);

    IDataService dataService = new DataService(mockedDataServiceA.Object,
                                            mockedDataServiceB.Object,
                                            mockedDataServiceC.Object,
                                            mockedDataServiceD.Object,);

    DateTime startTime = DateTime.Now;
    IEnumerable<ReturnData> data = await dataService.GetData();
    DateTime endTime = DateTime.Now;

    TimeSpan timeDiff = endTime.Subtract(startTime);

    Assert.IsTrue(timeDiff.TotalSeconds < 4);
}

我尝试过同时使用Thread.Sleep(1000)Task.Delay(1000)

两者都没有给出想要的结果。我希望测试只运行 1 秒多一点,因为每个方法都应该并行运行。

当使用Thread.Sleep(1000) 时,测试会运行 4.1 秒(测试失败)

使用Task.Delay(1000) 时,测试运行时间少于 100 毫秒。这在技术上可以通过我拥有的断言测试,但不是预期的结果。

我知道当我运行我的 Web 服务时,这些方法确实是并行运行的,我只是想要一个单元测试来证明该功能,并在将来有任何代码更改时保护该功能。

【问题讨论】:

  • 在每个Task开始时设置一个bool,然后在100ms后检查是否设置了所有4个bool
  • 仅供参考,您可以使用returnData.AddRange(await Task.WhenAll(dataTypeA, dataTypeB, dataTypeC, dataTypeD));,我至少建议改为使用returnData.AddRange(await dataTypeA)
  • 使用 Task.Delay 而不是 Thread.Sleep,但不要让您的测试方法异步,而是以 var result = dataService.GetData().Wait() 而不是 await dataService.GetData() 调用您的 GetData 方法,即保持测试方法同步。
  • @Zenilogix 我也会尝试你的建议。我确实尝试过 Task.Delay ,但没有按照您建议的方式调用该方法。我会让你知道结果。
  • @juharr 这可能在大多数情况下都有效,但我只是举例说明了我的代码是如何设置的。在我最终返回数据之前,在 .AddRange() 之后有一点代码。它与我的问题无关,因此我将其从示例代码中排除。

标签: c# unit-testing asynchronous


【解决方案1】:

使用 TaskCompletionSource 控制任务何时完成。

请注意,此测试假定 GetDataAsync 调用返回该方法等待的第一个任务。如果测试等待其他任务,那么您需要确保在调用所有 GetDataAsync 方法之前不调用 mock.Verify(),可能通过在模拟回调中减少 CountdownEvent

public class UnitTest1
{
    [Fact]
    public async Task Test1()
    {
        // TCS allows the test to control when the tasks complete
        // without needing to use Sleep or Delay (which will make the
        // test take unnecessarily long and probably introduces a race
        // condition)
        var (mockA, tcsA) = CreateMock();
        var (mockB, tcsB) = CreateMock();
        var (mockC, tcsC) = CreateMock();

        var service = new DataService(mockA.Object, mockB.Object, mockC.Object);

        var task = service.GetAllDataAsync();

        // Verify the method hasn't finished yet (AKA it's awaiting the tasks)
        Assert.NotEqual(TaskStatus.RanToCompletion, task.Status);

        // Verify that all the methods have been called
        mockA.Verify(m => m.GetDataAsync());
        mockB.Verify(m => m.GetDataAsync());
        mockC.Verify(m => m.GetDataAsync());

        // Now complete all the tasks and verify the method completes
        tcsA.SetResult(null);
        tcsB.SetResult(null);
        tcsC.SetResult(null);
        await task;
    }

    (Mock<IGetData>, TaskCompletionSource<object>) CreateMock()
    {
        var tcs = new TaskCompletionSource<object>();
        var mock = new Mock<IGetData>();
        mock.Setup(m => m.GetDataAsync()).Returns(tcs.Task);
        return (mock, tcs);
    }
}

public interface IGetData
{
    Task<object> GetDataAsync();
}

class DataService
{
    private IGetData _serviceA;
    private IGetData _serviceB;
    private IGetData _serviceC;
    public DataService(IGetData serviceA, IGetData serviceB, IGetData serviceC)
    {
        _serviceA = serviceA;
        _serviceB = serviceB;
        _serviceC = serviceC;
    }

    public async Task GetAllDataAsync()
    {
        var taskA = _serviceA.GetDataAsync();
        var taskB = _serviceB.GetDataAsync();
        var taskC = _serviceC.GetDataAsync();

        await Task.WhenAll(taskA, taskB, taskC);

        // do something with results;
    }
}

【讨论】:

  • 感谢您提供的信息。我会试试这个。
猜你喜欢
  • 1970-01-01
  • 2011-11-07
  • 2020-07-14
  • 1970-01-01
  • 2015-08-12
  • 2022-10-01
  • 2014-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多