0

我正在尝试围绕异步发布/订阅系统编写单元测试。在我的单元测试中,我创建一个TaskCompletionSource<int>并在订阅回调中为其分配一个值。在订阅回调中,我取消订阅出版物。下次我发布时,我想验证回调是否从未被击中。

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public async Task DomainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    var completionTask = new TaskCompletionSource<int>();

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            completionTask.TrySetResult(1);
            subscription.Unsubscribe();
            return completionTask.Task;
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the first result
    int firstResult = completionTask.Task.Result;

    // Publish the second message
    completionTask = new TaskCompletionSource<int>();
    DomainEvents.Publish(new FakeDomainEvent(content));
    await completionTask.Task;

    // Get the second result
    int secondResult = completionTask.Task.Result;

    // Assert
    Assert.AreEqual(1, firstResult, "The first result did not receive the expected value from the subscription delegate.");
    Assert.AreEqual(default(int), secondResult, "The second result had a value assigned to it when it shouldn't have. The unsubscription did not work.");
}

当我这样做时,测试挂在第二个await。我知道这是由于任务永远不会返回而发生的。我不确定如何解决它。我知道我可以轻松地创建一个本地字段,我只需像这样分配值:

[TestMethod]
[Owner("Johnathon Sullinger")]
[TestCategory("Domain")]
[TestCategory("Domain - Events")]
public void omainEvents_subscription_stops_receiving_messages_after_unsubscribing()
{
    // Arrange
    string content = "Domain Test";
    int callbackResult = 0;

    DomainEvents.Subscribe<FakeDomainEvent>(
        (domainEvent, subscription) =>
        {
            // Set the completion source so the awaited task can fetch the result.
            callbackResult++;
            subscription.Unsubscribe();
            return Task.FromResult(callbackResult);
        });

    // Act
    // Publish the first message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Publish the second message
    DomainEvents.Publish(new FakeDomainEvent(content));

    // Assert
    Assert.AreEqual(1, firstResult, "The callback was hit more than expected, or not hit at all.");
}

不过这感觉不对。这假设我从未在整个堆栈中执行等待操作(当他们是订阅者时我会这样做)。此测试不是安全测试,因为测试可能会在发布完全完成之前完成。这里的意图是我的回调是异步的,并且发布是非阻塞的后台进程。

在这种情况下如何处理 CompletionSource?

4

1 回答 1

1

很难测试某事永远不会发生。你能做的最好的事情就是测试它没有在合理的时间内发生。我有一个异步协调原语库,为了对这个场景进行单元测试,我不得不求助于只观察任务一段时间,然后假设成功(参见 参考资料AssertEx.NeverCompletesAsync)。

不过,这不是唯一的解决方案。也许从逻辑上讲,最干净的解决方案是伪造时间本身。也就是说,如果你的系统有足够的假时间系统的钩子,那么你实际上可以编写一个测试来确保永远不会调用回调。这听起来很奇怪,但它非常强大。缺点是它需要大量的代码修改——不仅仅是返回一个Task. 如果您有兴趣,可以从 Rx 开始,使用它们的TestScheduler类型

于 2016-01-23T04:03:38.980 回答