C# 单元测试异步事件

C# 单元测试异步事件,c#,multithreading,unit-testing,asynchronous,C#,Multithreading,Unit Testing,Asynchronous,我有一个设置,其中更改类的变量会引发一个异步运行的事件,我正试图对该事件进行单元测试 class A { Action<int> OnIntChange; private int _a; public int a { set { OnIntChange(value); } } } class B { RaiseAsyncEvent(int value) { Task

我有一个设置,其中更改类的变量会引发一个异步运行的事件,我正试图对该事件进行单元测试

class A {

    Action<int> OnIntChange;

    private int _a;
    public int a {
        set {
            OnIntChange(value);
        }
    }
}

class B {

    RaiseAsyncEvent(int value) {
        Task.Factory.StartNew(() => {c = value;});
    }

    int c;
}
然而,现在这种做法失败了。我不能使用,因为我不是直接提出这个事件,如果没有一些代码重组,我将无法使用它。似乎我唯一的选择就是在调用a.a=10之后添加一个System.Threading.Thread.Sleep()调用,但我正在寻找其他选项。建议

~~~~~~~~~~~~编辑~~~~~~~~~~~

我正在研究的一个潜在解决方案是让RaiseAsynceEvent返回它创建的任务,并为该任务设置一个字段。比如:

class B {

    Task task;

    RaiseAsyncEvent(int value) {
        task = Task.Factory.StartNew(() => {c = value;});
    }

    int c;
}


B b = new B();
A.OnIntChange += b.RaiseAsyncEvent; 
b.task.Wait();
A.a = 10;
Assert.AreEqual(10, A.b.c);
~~~~~~~~~~~~~进一步编辑~~~~~~~~~~

为了解决这个问题,我最终将B改为:

class B {

    bool RunAsync = true;

    RaiseAsyncEvent(int value) {
        Task task = Task.Factory.StartNew(() => {c = value;});
        if(!RunAsync) { task.Wait();}
    }

    int c;
}
并将测试更改为:

B b = new B();
b.RunAsync = false;
A.OnIntChange += b.RaiseAsyncEvent;    
A.a = 10;
Assert.AreEqual(10, A.b.c);
您可以在B的构造函数中通过a。自定义版本的
TaskScheduler
可以等待单元测试中所有任务的完成

class B {
   public B(TaskScheduler taskScheduler)
   {
      _taskScheduler = taskScheduler;
   }   


   public B(): this(TaskScheduler.Default)
   {
   }


   public void RaiseAsyncEvent(int value)
   {
       Task.Factory.StartNew(() => {c = value;}, CancellationToken.None,
         TaskCreationOptions.DenyChildAttach, _taskScheduler);
   }

   TaskScheduler _taskScheduler;

}
有能力等待我们寻求的完成

// Unit test

 var schedulerPair = new ConcurrentExclusiveSchedulerPair();

 B b = new B(schedulerPair.ConcurrentScheduler);
 A.OnIntChange += b.RaiseAsyncEvent;    
 A.a = 10;

 schedulerPair.Complete();
 schedulerPair.Completion.Wait();

 Assert.AreEqual(10, A.b.c);
当您需要关注多个异步操作时,它可以扩展到更高级的场景:

 B b1 = new B(schedulerPair.ConcurrentScheduler);
 B b2 = new B(schedulerPair.ConcurrentScheduler);
 B b3 = new B(schedulerPair.ConcurrentScheduler);

 A.OnIntChange += b1.RaiseAsyncEvent;    
 A.OnIntChange += b2.RaiseAsyncEvent;    
 A.OnIntChange += b3.RaiseAsyncEvent;    
 A.a = 10;

 schedulerPair.Complete();
 schedulerPair.Completion.Wait();

 Assert.AreEqual(10, b1.c);
 Assert.AreEqual(10, b2.c);
 Assert.AreEqual(10, b3.c);
编辑:
TaskScheduler
实例也可以使用静态属性共享,在这种情况下
B
构造函数签名不会更改

static class EventScheduler
{

  public static TaskScheduler TaskScheduler
  {
      get {return _taskScheduler; }
      set {_taskScheduler = value; }
  }   

  static TaskScheduler _taskScheduler = TaskScheduler.Default;

  public static Task RunAsync(Action<T> action)
  {
      return Task.Factory.StartNew(() => {c = value;}, 
       CancellationToken.None, TaskCreationOptions.DenyChildAttach,
      _taskScheduler);
   }
}

class B 
{
   public void RaiseAsyncEvent(int value)
   {
       EventScheduler.RunAsync(()=>{c = value;});
   }
 }

 // Unit test

 var schedulerPair = new ConcurrentExclusiveSchedulerPair();
 EventScheduler.TaskScheduler = schedulerPair.ConcurrentScheduler;

 B b = new B();
 A.OnIntChange += b.RaiseAsyncEvent;    
 A.a = 10;

 schedulerPair.Complete();
 schedulerPair.Completion.Wait();

 Assert.AreEqual(10, A.b.c);
静态类事件调度器
{
公共静态任务调度器任务调度器
{
获取{return\u taskScheduler;}
设置{u taskScheduler=value;}
}   
静态任务调度器_TaskScheduler=TaskScheduler.Default;
公共静态任务RunAsync(操作)
{
返回Task.Factory.StartNew(()=>{c=value;},
CancellationToken.None,TaskCreationOptions.Denychildatach,
_任务调度器);
}
}
B类
{
公共无效RaiseSyncEvent(int值)
{
RunAsync(()=>{c=value;});
}
}
//单元测试
var schedulerPair=新的ConcurrentExclusiveSchedulerPair();
EventScheduler.TaskScheduler=schedulerPair.ConcurrentScheduler;
B=新的B();
A.OnIntChange+=b.raisesyncEvent;
A.A=10;
schedulerPair.Complete();
schedulerPair.Completion.Wait();
主张平等(公元前10年);

我喜欢这个答案,并将其标记为已被接受的答案-如果我必须以我提出的方式解决我的问题,这将是正确的。然而,我最后做的是通过在B上添加一个“RunAsync”bool字段,在RaiseAsyncEvent中添加if(!RunAsync){task.wait();},并设置B.RunAsync=false,强制任务同步运行;在测试中初始化。不是最漂亮的,但它是一个简单的解决方案,它可能是一个非常复杂的问题。如果你想避免改变构造函数签名,就考虑一个由所有事件共享的TaskPalthor(一个StutLon)的静态实例。然后,不用在所有对象上设置RunAsync属性,只需将ConcurrentScheduler分配给静态属性。我正在使用的代码比我在这里所展示的要复杂得多——我实际上是在实现我自己的静态回调管理器,我将事件传递给它,它会生成一个新任务,该任务根据事件异步或同步运行耦合因子,保存句柄,并处理返回逻辑。我添加的bool允许我绕过异步逻辑,并强制它从一开始就同步,这样我的所有单元测试仍然可以通过。由于管理器是静态的,我只需要在测试开始时对其RunAsync bool进行一次更改,就可以正常运行了。
static class EventScheduler
{

  public static TaskScheduler TaskScheduler
  {
      get {return _taskScheduler; }
      set {_taskScheduler = value; }
  }   

  static TaskScheduler _taskScheduler = TaskScheduler.Default;

  public static Task RunAsync(Action<T> action)
  {
      return Task.Factory.StartNew(() => {c = value;}, 
       CancellationToken.None, TaskCreationOptions.DenyChildAttach,
      _taskScheduler);
   }
}

class B 
{
   public void RaiseAsyncEvent(int value)
   {
       EventScheduler.RunAsync(()=>{c = value;});
   }
 }

 // Unit test

 var schedulerPair = new ConcurrentExclusiveSchedulerPair();
 EventScheduler.TaskScheduler = schedulerPair.ConcurrentScheduler;

 B b = new B();
 A.OnIntChange += b.RaiseAsyncEvent;    
 A.a = 10;

 schedulerPair.Complete();
 schedulerPair.Completion.Wait();

 Assert.AreEqual(10, A.b.c);