2016-09-15 61 views
1
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类执行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个值,则测试失败,并且预期消息:至少一次,但发生0次

据我所知,断言发生在值由另一个线程排队之前,但是如何测试这种行为?

回答

0

,我来到了我的解决办法是重新设计Composer类或更具体的变化Send方法异步:

public Task SendAsync(IValue value) 
{ 

} 

背后的思想是返回Task给定值时,在“背景”前锋组成,这将完成线。

单元测试只需要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增加了额外的价值。偶尔测试失败,如果它运行乘法时间。 我认为原因是线程池“不可预知”的工作。

我仍然不确定如何处理Task需要在启动实例的整个生命周期中运行,但这将是另一个问题。

+1

至少在当前版本的Visual Studio中,Windows测试支持异步/等待。只需等待你在普通代码中的方法。使你的测试方法做'公共异步任务Send_ValidMessage_ExecuteWriteMethodWithGivenValue()',然后把'.Wait()'调用到'await'。 –

0

你需要建立某种“连接”。像这样的东西应该就足够了:

public Task JoinAsync() 
{ 
    this.IsConnected = false; 
    return _ComposerTask; 
} 

请注意,您也应该在您的生产代码中使用它。如果你的代码最终没有观察到_ComposerTask,那么Start抛出的任何异常都将被无声地吞噬。

+0

对不起,我觉得自己非常不安全,因为只是因为测试需要向类的公共API添加一些行为。 – Fabio

+0

@Fabio:正如我在我的回答中指出的那样,这段代码也应该在你系统的其他地方使用。作为一般规则,应该等待所有的任务。 –