C# 异步初始化及其单元测试
背景 我需要一些类来执行后台初始化,应该从构造函数开始。目前,我正在使用一个由构造函数启动的C# 异步初始化及其单元测试,c#,.net,unit-testing,asynchronous,initialization,C#,.net,Unit Testing,Asynchronous,Initialization,背景 我需要一些类来执行后台初始化,应该从构造函数开始。目前,我正在使用一个由构造函数启动的任务,然后根据初始化情况,所有操作都将等待该任务完成 请看下面的简化示例: interface IEntry {} interface IRepository { IQueryable<IEntry> Query { get; } void Add(IEntry entry); } class PrefetchedRepository : IRepository {
任务
,然后根据初始化情况,所有操作都将等待该任务
完成
请看下面的简化示例:
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);
}
}
}
EnsurePrefetchCompleted
方法公开为内部方法,并在单元测试中使用它(假设使用[程序集:InternalsVisibleTo(“…”)
)有没有更简单的方法来进行这种测试?将预取逻辑提取到一个单独的预取器类中,并且在测试时,使用一些不使用单独线程的方法来模拟预取器 这将允许您对预回迁存储库进行白盒测试,我看到您正在尝试使用它
underyingrepositorymock.CallsTo(r=>r.Query).musthavebefored(Repeated.justice(1))代码>(我永远不会做白盒测试,但那只是我自己。)
完成白盒测试后,就可以对预回迁存储库进行黑盒测试,而不必关心它内部的工作方式。(它是否调用其他对象来完成它的工作,调用它们的次数,等等)因此,测试代码不需要猜测检查查询是否被调用的时间点,因为它根本不关心查询是否被调用。基本上,您的测试代码将针对接口IRepository
进行测试,而不是针对类预取存储库
不要公开处于无效状态的实例。在调用PrefetchedRepository
中的任何成员时,客户端代码可能经常会注意到延迟,这仅仅是因为底层存储库速度较慢。您正试图通过隐藏客户机代码甚至都不知道的EnsurePrefetchCompleted
中所有糟糕的等待逻辑来隐藏这些细节,从而变得更聪明。但这可能会让客户感到惊讶,为什么连这都要花很多时间
更好的方法是在API的公共界面中公开任务
,并让客户端代码在对存储库实例执行任何操作之前等待它
大概是这样的:
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 Task Initialization
{
get
{
return _prefetchingTask;
}
}
...
}
当然,删除EnsurePrefetchCompleted
方法和对它的所有调用
但我知道这会带来所谓的气味
更好的设计是引进一家工厂为你做这件事
public class PrefetchedRepositoryFactory
{
public Task<IRepository> CreateAsync()
{
//someOtherSlowRepo can be a parameter or instance field of this class
var repo = new PrefetchedRepository(someOtherSlowRepo);
await repo.Initialization;
return repo;
}
}
如果您这样做,您就不必特别注意测试,因为您将始终拥有完整构建的存储库
你可以等待里面的测试方法;大多数主要的单元测试框架都支持异步任务
返回方法。这肯定会解决存储库测试的问题。但是在这种情况下,我应该如何测试真正的异步预取器呢;请让我知道它是否清晰。很好的解决方案,但它涉及到改变我接收存储库实例的方式。我不希望改变这一点,而是限制实施范围的改变。至于慢度和其他注意事项-PrefetchingRepository
只是一些后台初始化的一个例子,我需要从构造函数开始。我真正的问题不是数据获取,只是要补充一点,如果这是一些遗留代码,我只会使用反射来调用私有EnsurePrefetchCompleted()方法。关于你的第一个问题,我描述一下/@StephenCleary,谢谢!它真的很有用!
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 Task Initialization
{
get
{
return _prefetchingTask;
}
}
...
}
var repo = new PrefetchedRepository(someOtherSlowRepo);
await repo.Initialization;
//Then do whatever with the repo.
public class PrefetchedRepositoryFactory
{
public Task<IRepository> CreateAsync()
{
//someOtherSlowRepo can be a parameter or instance field of this class
var repo = new PrefetchedRepository(someOtherSlowRepo);
await repo.Initialization;
return repo;
}
}
var repo = await prefetchedRepositoryFactory.CreateAsync();
//Do whatever with repo.