gpt4 book ai didi

c# - 异步初始化及其单元测试

转载 作者:太空宇宙 更新时间:2023-11-03 13:11:32 25 4
gpt4 key购买 nike

背景我需要一些类来执行后台初始化,它应该从构造函数开始。目前我正在使用一个由构造函数启动的 Task,然后所有操作,取决于该初始化等待该 Task 完成。

请看下面的简化示例:

interface IEntry {}

interface IRepository
{
IQueryable<IEntry> Query { get; }
void Add(IEntry entry);
}

class PrefetchedRepository : IRepository
{
private readonly Task _prefetchingTask;
private readonly ICollection<IEntry> _entries = new List<IEntry>();
private readonly IRepository _underlyingRepository;

public PrefetchedRepository(IRepository underlyingRepository)
{
_underlyingRepository = underlyingRepository;
// Background initialization starts here
_prefetchingTask = Task.Factory.StartNew(Prefetch);
}

public IQueryable<IEntry> Query
{
get
{
EnsurePrefetchCompleted();
return _entries.AsQueryable();
}
}

public void Add(IEntry entry)
{
EnsurePrefetchCompleted();
_entries.Add(entry);
_underlyingRepository.Add(entry);
}

private void EnsurePrefetchCompleted()
{
_prefetchingTask.Wait();
}

private void Prefetch()
{
foreach (var entry in _underlyingRepository.Query)
{
_entries.Add(entry);
}
}
}

这行得通。当我想在单元测试中测试初始化​​时,问题就开始了。我正在创建实例并提供底层存储库的模拟。我想确保所有条目都按预期从模拟中获取。

[TestFixture]
public class PrefetchingRepositoryTests
{
[Test]
public void WhenInitialized_PrefetchingIsDone()
{
// Arrange
var underlyingRepositoryMock = A.Fake<IRepository>();

// Act
var target = new PrefetchedRepository(_underlyingRepository);

// Assert
underlyingRepositoryMock.CallsTo(r => r.Query).MustHaveHappened(Repeated.Exactly(1));
}
}

正如您所想象的,大部分时间都失败了,因为实际上初始化并没有在断言点开始。

问题

问题 1 - 初始化:是否有比在构造函数中启动任务并在所有相关操作中等待它更优雅的异步初始化方式?

问题 2 - 测试:我想到了 2 种可能的方法来解决测试和测试者之间的竞争:

  1. 使用事件句柄进行测试:

    [Test]
    public void WhenInitialized_PrefetchingIsDone()
    {
    // Arrange ...
    var invokedEvent = new ManualResetEvent(false);
    underlyingRepositoryMock.CallsTo(r => r.Query).Invokes(_ => invokedEvent.Set());

    // Act ...

    // Assert
    Assert.True(invokedEvent.WaitOne(1000));
    }
  2. EnsurePrefetchCompleted 方法公开为内部方法并在单元测试中使用它(假设使用 [assembly: InternalsVisibleTo("...")])

这两种解决方案的问题是,如果故障持续时间很长(实际上是第二种情况 - 它受测试超时限制)。

有没有更简单的方法来进行这种测试?

最佳答案

将预取逻辑提取到单独的 Prefetcher 类中,并在测试时使用无需使用单独线程即可执行提取的东西模拟 Prefetcher。

这将允许您对 PrefetchedRepository 进行白盒测试,我看到您正在尝试使用它 underlyingRepositoryMock.CallsTo(r => r.Query).MustHaveHappened(Repeated.Exactly(1));(我永远不会做白盒测试,但那只是我。)

完成白盒测试后,您就可以对 PrefetchedRepository 进行黑盒测试,而无需关心它的内部工作方式。 (它是否调用其他对象来完成它的工作,调用它们的次数等)因此,您的测试代码将不需要猜测可以检查查询是否已被调用的时间点,因为它会根本不关心查询是否被调用。本质上,您的测试代码将针对 interface IRepository 进行测试,而不是针对 class PrefetchedRepository

关于c# - 异步初始化及其单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28392933/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com