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);