C# 单元测试异步函数

C# 单元测试异步函数,c#,.net,unit-testing,asynchronous,C#,.net,Unit Testing,Asynchronous,在下面的代码示例中,我有一个异步计算器类。这是注入一个ICalc,这将是一个同步计算器。我使用依赖注入和模拟ICalc,因为这类似于我的真实场景,尽管我猜模拟与问题并不相关。AsyncCalc有一个函数,该函数将异步调用另一个函数-将回调作为参数。当异步函数调用完成时,将触发回调并返回结果 现在我想测试我的异步函数-检查回调是否用预期参数触发。这个代码似乎有效。然而,我觉得它可能会在任何时候爆炸——我关心的是回调的竞争条件,以在函数结束和测试终止之前完成——因为这将在单独的线程中运行 我现在的问

在下面的代码示例中,我有一个异步计算器类。这是注入一个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);
}
公共接口ICalc
{
整数地址编号(整数a、整数b);
}
公共类异步计算
{
私有只读ICalc_calc;
公共委托无效结果处理器(int result);
公共委托int AddNumbersAsyncCaller(INTA、INTB);
公共异步计算(ICalc calc)
{
_计算量=计算量;
}
public void addnumber(int a、int b、ResultProcessor ResultProcessor)
{
var caller=新的AddNumbersAsyncCaller(_calc.AddNumbers);
BeginInvoke(a、b、新的异步回调(AddNumbersCallbackMethod)、resultProcessor);
}
public void AddNumbersCallbackMethod(IAsyncResult ar)
{
var result=(AsyncResult)ar;
var caller=(AddNumbersAsyncCaller)result.AsyncDelegate;
var resultFromAdd=caller.EndInvoke(ar);
var resultProcessor=ar.asynchState作为resultProcessor;
if(resultProcessor==null)返回;
结果处理器(resultFromAdd);
}             
}
[测试]
公共无效测试SyncCalc()
{
var mocks=new MockRepository();
var fakeCalc=mocks.DynamicMock();
使用(mocks.Record())
{
假计算地址编号(1,2);
最后一次呼叫返回(3);
}
var asyncCalc=新的asyncCalc(fakeCalc);
asyncCalc.AddNumber(1,2,TestResultProcessor);
}
公共void测试结果处理器(int-result)
{
断言。等于(3,结果);
}
您可以使用来同步线程

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

[测试]
公共无效测试SyncCalc()
{
var mocks=new MockRepository();
var fakeCalc=mocks.DynamicMock();
使用(mocks.Record())
{
假计算地址编号(1,2);
最后一次呼叫返回(3);
}
var asyncCalc=新的asyncCalc(fakeCalc);
var完成=新手动重置事件(错误);
int结果=0;
asyncCalc.AddNumbers(1,2,r=>{result=r;completion.Set();});
完成。WaitOne();
断言.AreEqual(3,calcResult);
}
//**改用匿名方法
//公共void测试结果处理器(int-result)
// {
//断言。等于(3,结果);
// }
您还可以使用“testrunner”类在循环中运行断言。循环将在try/catch中运行断言。异常处理程序将尝试再次运行断言,直到超时过期。我最近写了一篇关于这种技术的博客文章。该示例使用groovy,但适用于任何语言。不是传递闭包,而是在c#中传递操作


我认为您的测试只需在循环中等待回调执行即可。真是太棒了!工作完美。正是我需要的。谢谢!我只想补充一点,对于某些场景(外部服务),您可能需要添加一个超时:Assert.IsTrue(completion.WaitOne(TimeSpan.FromSeconds(10)),“Calculation completed”);
[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);
// }