我正在 MSTest 中为 WPF 应用程序编写测试,其中包含对 async void 方法(即发即弃模式)的调用。
我的目标是在测试期间同步运行此代码。
这可能吗?
我尝试过“同步”SynchronizationContext:
public class SynchronousSynchronizationContext : SynchronizationContext
{
private readonly ConcurrentQueue<(SendOrPostCallback, object)> _workItems = new ConcurrentQueue<(SendOrPostCallback, object)>();
private readonly AutoResetEvent _workItemsAvailable = new AutoResetEvent(false);
public override void Post(SendOrPostCallback d, object state)
{
Trace.WriteLine("SynchronousSynchronizationContext.Post(), start");
// Execute the callback immediately
d(state);
Trace.WriteLine("SynchronousSynchronizationContext.Post(), end");
}
/* enqueues the work item to be completed later:
public override void Post(SendOrPostCallback d, object state)
{
Trace.WriteLine("SynchronousSynchronizationContext.Post()");
_workItems.Enqueue((d, state));
_workItemsAvailable.Set();
}
*/
public override void Send(SendOrPostCallback d, object state)
{
Trace.WriteLine("SynchronousSynchronizationContext.Send()");
d(state);
}
public void Run()
{
Trace.WriteLine("SynchronousSynchronizationContext.Run()");
while (_workItems.TryDequeue(out var workItem))
{
workItem.Item1(workItem.Item2);
}
}
public void Complete()
{
Trace.WriteLine("SynchronousSynchronizationContext.Complete()");
while (_workItems.TryDequeue(out var workItem))
{
Trace.WriteLine("SynchronousSynchronizationContext, execute callback");
// execute callback.
// The workItem is a tuple where Item1 is the callback and Item2 is the state object.
workItem.Item1(workItem.Item2);
}
}
}
但这不起作用。
Post()
方法不会像我想要的那样同步运行回调。
相反,我必须在测试方法中的各个点显式调用
Complete()
:
private SynchronousSynchronizationContext _syncContext;
[TestInitialize]
public void TestInitialize()
{
_syncContext = new SynchronousSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(_syncContext);
}
[TestMethod]
public void MyTest_ReturnsData()
{
// ACT
MyTest();
// Finish all fire-and-forget tasks:
_syncContext.Complete();
// ASSERT
...
}
有没有办法同步运行代码而不需要调用
Complete()
?
我完全赞成将
async void
转发给 async Task
,但是针对这个问题。
每个测试我们可以有一个
SynchronizationContext
实例,并依靠 async void
状态机调用 OperationCompleted
,我们可以在其中发出方法已完成的信号。我们还获得了异常 Post
,我们可以将其包装并重新抛出到我们将介绍的阻塞 WaitForCompletion
方法中。
void Main() { // or test method
var syncContext = new AsyncVoidSyncContext();
// to be instantiated in every test method
SynchronizationContext.SetSynchronizationContext(syncContext);
AsyncVoidMethod();
syncContext.WaitForCompletion();
}
async void AsyncVoidMethod() {
await Task.Delay(3000);
ThrowException();
Console.WriteLine("Bar");
}
public void ThrowException() {
throw new Exception("Foo");
}
public sealed class AsyncVoidSyncContext : SynchronizationContext {
ManualResetEventSlim _mres = new ManualResetEventSlim(false);
ExceptionDispatchInfo _exceptionDispatchInfo;
public void WaitForCompletion() {
_mres.Wait();
_exceptionDispatchInfo?.Throw();
}
public override void Post(SendOrPostCallback d, object state) {
try {
d(state);
} catch (Exception ex) {
_exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
}
}
public override void OperationCompleted() {
_mres.Set();
}
}