【问题标题】:Unit testing asynchronous function单元测试异步函数
【发布时间】:2010-01-21 23:08:33
【问题描述】:

在以下代码示例中,我有一个异步计算器类。这是用 ICalc 注入的,这将是一个同步计算器。我使用依赖注入并模拟 ICalc,因为这类似于我的真实场景,尽管我猜模拟与问题并不真正相关。 AsyncCalc 有一个函数,它将异步调用另一个函数 - 将回调作为参数。当异步函数调用完成时,回调将被触发。

现在我想测试我的异步函数 - 检查回调是否由预期的参数触发。这段代码似乎有效。但是,我觉得它可能随时会爆炸——我担心的是回调的竞争条件在函数结束和测试终止之前完成——因为这将在单独的线程中运行。

我现在的问题是我是否在正确的轨道上对异步功能进行单元测试,或者是否有人可以帮助我走上正确的轨道..?如果我能确保立即触发回调——最好是在同一个线程上,我猜会有什么感觉更好?可以/应该做吗?

public interface ICalc
{
    int AddNumbers(int a, int b);
}

public class AsyncCalc
{
    private readonly ICalc _calc;
    public delegate void ResultProcessor(int result);
    public delegate int AddNumbersAsyncCaller(int a, int b);

    public AsyncCalc(ICalc calc)
    {
        _calc = calc; 
    }

    public void AddNumbers(int a, int b, ResultProcessor resultProcessor)
    {
        var caller = new AddNumbersAsyncCaller(_calc.AddNumbers);
        caller.BeginInvoke(a, b, new AsyncCallback(AddNumbersCallbackMethod), resultProcessor);
    }

    public void AddNumbersCallbackMethod(IAsyncResult ar)
    {
        var result = (AsyncResult)ar;
        var caller = (AddNumbersAsyncCaller)result.AsyncDelegate;
        var resultFromAdd = caller.EndInvoke(ar);

        var resultProcessor = ar.AsyncState as ResultProcessor;
        if (resultProcessor == null) return;

        resultProcessor(resultFromAdd);
    }             
}

[Test]
public void TestingAsyncCalc()
{
    var mocks = new MockRepository();
    var fakeCalc = mocks.DynamicMock<ICalc>();

    using (mocks.Record())
    {
        fakeCalc.AddNumbers(1, 2);
        LastCall.Return(3);
    }

    var asyncCalc = new AsyncCalc(fakeCalc);
    asyncCalc.AddNumbers(1, 2, TestResultProcessor);
}

public void TestResultProcessor(int result)
{
    Assert.AreEqual(3, result);
}

【问题讨论】:

  • 我认为您的测试只需循环等待直到回调执行。

标签: c# .net unit-testing asynchronous


【解决方案1】:

您可以使用ManualResetEvent 来同步您的线程。

在以下示例中,测试线程将阻塞对completion.WaitOne() 的调用。 异步计算的回调存储结果并然后通过调用completion.Set() 发出事件信号。

[Test]
public void TestingAsyncCalc()
{
    var mocks = new MockRepository();
    var fakeCalc = mocks.DynamicMock<ICalc>();

    using (mocks.Record())
    {
        fakeCalc.AddNumbers(1, 2);
        LastCall.Return(3);
    }

    var asyncCalc = new AsyncCalc(fakeCalc);

    var completion = new ManualResetEvent(false);
    int result = 0;
    asyncCalc.AddNumbers(1, 2, r => { result = r; completion.Set(); });
    completion.WaitOne();

    Assert.AreEqual(3, calcResult);
}

// ** USING AN ANONYMOUS METHOD INSTEAD
// public void TestResultProcessor(int result)
// {
//     Assert.AreEqual(3, result);
// }

【讨论】:

  • 太棒了!完美运行。正是我需要的。谢谢!
  • 我只会添加对于某些场景(外部服务)您可能想要添加超时: Assert.IsTrue(completion.WaitOne(TimeSpan.FromSeconds(10)), "Calculation completed");
【解决方案2】:

您还可以使用“测试运行器”类在循环中运行断言。该循环将在 try/catch 中运行断言。异常处理程序将简单地尝试再次运行断言,直到超时到期。我最近写了一篇关于这种技术的博客文章。该示例是 groovy 的,但适用于任何语言。你可以在 c# 中传递一个 Action,而不是传递一个闭包。

http://www.greenmoonsoftware.com/2013/08/asynchronous-functional-testing/

【讨论】:

    猜你喜欢
    • 2018-11-18
    • 1970-01-01
    • 1970-01-01
    • 2012-07-15
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 2016-08-03
    • 2016-05-08
    相关资源
    最近更新 更多