单元测试异步代码
Unit testing asynchronous code
我有一个简单的 class,方法很简单:
partial class SimpleClass
{
private readonly ISimpleManager _simpleManager;
public SimpleClass(ISimpleManager simpleManager)
{
_simpleManager = simpleManager;
}
public async void SimpleMethod()
{
IsInProgress = true;
DoSomeWork();
Task<int> hardWork0Task = _simpleManager.DoHardWork0Async();
Task<int> hardWork1Task = _simpleManager.DoHardWork1Async();
DoIndependentWork();
int hardWork0Result = await hardWork0Task.ConfigureAwait(false);
DoDependentWork(hardWork0Result);
int hardWork1Result = await hardWork1Task.ConfigureAwait(false);
DoDependentWork(hardWork1Result);
IsInProgress = false;
}
}
让我们假设 属性 IsInProgress
只是 bool
属性 通知 GUI 它的状态以允许刷新进度条。 DoSomeWork
、DoDependentWork
、DoIndependentWork
是一些使用或不用努力的方法。
在这种情况下,ISimpleManager
是您可以想象的最简单的界面:
interface ISimpleManager
{
Task<int> DoHardWork0Async();
Task<int> DoHardWork1Async();
}
我必须使用 Moq 和 NUnit 编写一些单元测试。我该如何为这种情况编写单元测试?我想检查 IsInProgress
属性 的状态是否在整个代码 运行 期间与 GUI 异步更改为 false。是否有意义?可能吗?如果我的 async
方法 returns Task
或通用 Task<T>
怎么办?如果我配置等待 true
怎么办?
以下是单元测试 IsInProgress
设置是否正确的方法:
创建可等待信号量:
var sem = new SemaphoreSlim(1);
使用以下实现创建存根 ISimpleManager
:
async Task<int> DoHardWork0Async() {
await sem.WaitAsync();
return 0;
}
致电SimpleMethod
。它会卡在 DoHardWork0Async
调用中的信号量上
验证IsInProgress
的效果
作为清理步骤,释放信号量:
sem.Release();
您只需要控制 ISimpleManager
任务何时完成。根据 Ilya 的回答,您可以使用 SemaphoreSlim
,或者直接使用 TaskCompletionSource<T>
。我通常更喜欢 TaskCompletionSource<T>
因为它更简单;但是,SemaphoreSlim
实例可以重复使用,而 TaskCompletionSource<T>
只能触发一次。
附带说明一下,您应该避免使用 async void
,除非该方法是事件处理程序。它应该 不 是任何类型的 "default" - 默认应该是 return Task
除非你绝对 不能。所以在这个例子中,SimpleMethod
当然应该returnTask
.
这是使用 TCS 的样子:
async Task MyTestMethod()
{
// Set up the mock object
var tcs0 = new TaskCompletionSource<int>();
var tcs1 = new TaskCompletionSource<int>();
var stub = new Mock<ISimpleManager>();
stub.Setup(x => x.DoHardWork0Async()).Returns(tcs0.Task);
stub.Setup(x => x.DoHardWork1Async()).Returns(tcs1.Task);
var sut = new SimpleClass(stub.Object);
var task = sut.SimpleMethod();
Assert.True(sut.InProgress);
tcs0.SetResult(7);
Assert.True(sut.InProgress);
tcs1.SetResult(13);
await task;
Assert.False(sut.InProgress);
}
我有一个简单的 class,方法很简单:
partial class SimpleClass
{
private readonly ISimpleManager _simpleManager;
public SimpleClass(ISimpleManager simpleManager)
{
_simpleManager = simpleManager;
}
public async void SimpleMethod()
{
IsInProgress = true;
DoSomeWork();
Task<int> hardWork0Task = _simpleManager.DoHardWork0Async();
Task<int> hardWork1Task = _simpleManager.DoHardWork1Async();
DoIndependentWork();
int hardWork0Result = await hardWork0Task.ConfigureAwait(false);
DoDependentWork(hardWork0Result);
int hardWork1Result = await hardWork1Task.ConfigureAwait(false);
DoDependentWork(hardWork1Result);
IsInProgress = false;
}
}
让我们假设 属性 IsInProgress
只是 bool
属性 通知 GUI 它的状态以允许刷新进度条。 DoSomeWork
、DoDependentWork
、DoIndependentWork
是一些使用或不用努力的方法。
ISimpleManager
是您可以想象的最简单的界面:
interface ISimpleManager
{
Task<int> DoHardWork0Async();
Task<int> DoHardWork1Async();
}
我必须使用 Moq 和 NUnit 编写一些单元测试。我该如何为这种情况编写单元测试?我想检查 IsInProgress
属性 的状态是否在整个代码 运行 期间与 GUI 异步更改为 false。是否有意义?可能吗?如果我的 async
方法 returns Task
或通用 Task<T>
怎么办?如果我配置等待 true
怎么办?
以下是单元测试 IsInProgress
设置是否正确的方法:
创建可等待信号量:
var sem = new SemaphoreSlim(1);
使用以下实现创建存根
ISimpleManager
:async Task<int> DoHardWork0Async() { await sem.WaitAsync(); return 0; }
致电
SimpleMethod
。它会卡在DoHardWork0Async
调用中的信号量上
验证
IsInProgress
的效果
作为清理步骤,释放信号量:
sem.Release();
您只需要控制 ISimpleManager
任务何时完成。根据 Ilya 的回答,您可以使用 SemaphoreSlim
,或者直接使用 TaskCompletionSource<T>
。我通常更喜欢 TaskCompletionSource<T>
因为它更简单;但是,SemaphoreSlim
实例可以重复使用,而 TaskCompletionSource<T>
只能触发一次。
附带说明一下,您应该避免使用 async void
,除非该方法是事件处理程序。它应该 不 是任何类型的 "default" - 默认应该是 return Task
除非你绝对 不能。所以在这个例子中,SimpleMethod
当然应该returnTask
.
这是使用 TCS 的样子:
async Task MyTestMethod()
{
// Set up the mock object
var tcs0 = new TaskCompletionSource<int>();
var tcs1 = new TaskCompletionSource<int>();
var stub = new Mock<ISimpleManager>();
stub.Setup(x => x.DoHardWork0Async()).Returns(tcs0.Task);
stub.Setup(x => x.DoHardWork1Async()).Returns(tcs1.Task);
var sut = new SimpleClass(stub.Object);
var task = sut.SimpleMethod();
Assert.True(sut.InProgress);
tcs0.SetResult(7);
Assert.True(sut.InProgress);
tcs1.SetResult(13);
await task;
Assert.False(sut.InProgress);
}