另一个线程上的单元测试方法
Unit testing methods on another thread
public class Composer
{
private Task _ComposerTask;
private ConcurrentQueue<IValue> _Values;
public bool IsConnected { get; }
// Other dependencies
private IClient _Client;
private IWriter _Writer
public Task async ConnectAsync()
{
this.IsConnected = await _Client.ConnectAsync();
_ComposerTask = Task.Run(() => this.Start());
}
private void Start()
{
while(this.IsConnected)
{
IValue value;
if(_Values.TryDequeue(out value) == false)
continue;
_Writer.Write(value);
}
}
public void Send(IValue value)
{
_Values.Enqueue(value);
}
}
连接成功后Composer
class异步执行Start
方法(在另一个线程上)。
Start
方法检查值队列并在值存在时将其转发。
我在测试 Send
方法时遇到问题。
[Test]
public void Send_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var fakeValue = Mock.Create<IValue>();
var fakeWriter = Mock.Create<IWriter>();
var fakeClient = Mock.Create<IClient>();
Mock.Arrange(() => fakeClient.ConnectAsync().Returns(CompletedTask);
var composer = new Composer(fakeClient, fakeWriter);
// for (int i = 0; i < 10; i++)
// {
// composer.Send(Mock.Create<IValue>());
// }
composer.ConnectAsync().Wait();
// Act
composer.Send(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
评论 for loop
测试通过。但是,如果执行 for loop
并且内部队列将在添加预期值之前填充甚至 10 个值,则测试失败并显示消息:expected at least once, but occurs 0 times。
据我所知,断言发生在值被另一个线程排队之前,但是如何测试这种行为?
您需要构建某种 "join"。这样的东西应该足够了:
public Task JoinAsync()
{
this.IsConnected = false;
return _ComposerTask;
}
请注意,您还应该在生产代码中使用它。如果您的代码最终没有观察到 _ComposerTask
,那么 Start
抛出的任何异常都将被默默地吞噬。
我想出的解决方案是重新设计 Composer
class 或者更具体地说,将 Send
方法更改为异步:
public Task SendAsync(IValue value)
{
}
背后的想法是 return Task
它将在给定值在 "background" 线程上向前组合时完成。
单元测试只需要await
直到任务完成并断言正确执行。
[Test]
public async Task SendAsync_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var composer = TestFactory.GenerateComposer();
// var tasks = new List<Task>();
// for (int i = 0; i < 10; i++)
// {
// tasks.Add(composer.SendAsync(Mock.Create<IValue>()));
// }
await composer.ConnectAsync();
// Act
await composer.SendAsync(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
即使没有在 for loop
中添加额外的值,我的原始单元测试也不成功。如果乘 运行 次,测试偶尔会失败。
我认为原因是 "unpredictable" 线程池的工作。
我仍然不确定如何处理 Task
需要 运行 在它启动的实例的整个生命周期内,但这将是另一个问题。
public class Composer
{
private Task _ComposerTask;
private ConcurrentQueue<IValue> _Values;
public bool IsConnected { get; }
// Other dependencies
private IClient _Client;
private IWriter _Writer
public Task async ConnectAsync()
{
this.IsConnected = await _Client.ConnectAsync();
_ComposerTask = Task.Run(() => this.Start());
}
private void Start()
{
while(this.IsConnected)
{
IValue value;
if(_Values.TryDequeue(out value) == false)
continue;
_Writer.Write(value);
}
}
public void Send(IValue value)
{
_Values.Enqueue(value);
}
}
连接成功后Composer
class异步执行Start
方法(在另一个线程上)。
Start
方法检查值队列并在值存在时将其转发。
我在测试 Send
方法时遇到问题。
[Test]
public void Send_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var fakeValue = Mock.Create<IValue>();
var fakeWriter = Mock.Create<IWriter>();
var fakeClient = Mock.Create<IClient>();
Mock.Arrange(() => fakeClient.ConnectAsync().Returns(CompletedTask);
var composer = new Composer(fakeClient, fakeWriter);
// for (int i = 0; i < 10; i++)
// {
// composer.Send(Mock.Create<IValue>());
// }
composer.ConnectAsync().Wait();
// Act
composer.Send(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
评论 for loop
测试通过。但是,如果执行 for loop
并且内部队列将在添加预期值之前填充甚至 10 个值,则测试失败并显示消息:expected at least once, but occurs 0 times。
据我所知,断言发生在值被另一个线程排队之前,但是如何测试这种行为?
您需要构建某种 "join"。这样的东西应该足够了:
public Task JoinAsync()
{
this.IsConnected = false;
return _ComposerTask;
}
请注意,您还应该在生产代码中使用它。如果您的代码最终没有观察到 _ComposerTask
,那么 Start
抛出的任何异常都将被默默地吞噬。
我想出的解决方案是重新设计 Composer
class 或者更具体地说,将 Send
方法更改为异步:
public Task SendAsync(IValue value)
{
}
背后的想法是 return Task
它将在给定值在 "background" 线程上向前组合时完成。
单元测试只需要await
直到任务完成并断言正确执行。
[Test]
public async Task SendAsync_ValidMessage_ExecuteWriteMethodWithGivenValue()
{
// Arrange
var composer = TestFactory.GenerateComposer();
// var tasks = new List<Task>();
// for (int i = 0; i < 10; i++)
// {
// tasks.Add(composer.SendAsync(Mock.Create<IValue>()));
// }
await composer.ConnectAsync();
// Act
await composer.SendAsync(fakeValue);
// Assert
Mock.Assert(() => fakeWriter.Write(fakeValue), Occurs.Once());
}
即使没有在 for loop
中添加额外的值,我的原始单元测试也不成功。如果乘 运行 次,测试偶尔会失败。
我认为原因是 "unpredictable" 线程池的工作。
我仍然不确定如何处理 Task
需要 运行 在它启动的实例的整个生命周期内,但这将是另一个问题。