c# - 单元测试组合根?

标签 c# unit-testing dependency-injection prism

我有一个由多个模块 (IModule) 组成的 PRISM 应用程序,其中 Bootstrap 将每个模块传递给 DI 容器,以便每个模块都能够注入(inject)/解析服务。这意味着每个模块都有自己的“组合根”,其中类型被注入(inject)/解析,我想知道关于单元测试它们的最佳实践是什么。

例如,假设我有一个资源模块,它负责创建和注册从各种数据源获取数据的服务。假设我按如下方式实现 IModule.Initialize 方法:

void Initialize()
{
ISomeDataService someDataService = _container.Resolve<SomeDataService>();
someDataService.Connect();
_container.RegisterInstance<ISomeDataService>(someDataService);
}

Resources 模块创建 SomeDataService 的一个实例,打开一个连接,并注册它以便其他模块可以使用它。注意:这实际上不是我的做法,这只是为了快速说明。

现在从单元测试的角度来看,我该如何测试 Initialize 方法?我想在这里测试两件事:

  1. ISomeDataService.Connect()正在调用方法。
  2. IUnityContainer.RegisterInstance正在调用并提供正确的服务。

Initialize()负责实际创建具体类型并注册它们,看来我在为它提供自己的 ISomeDataService 时运气不好 mock 。现在它确实尝试解析具体类型 SomeDataService (这与执行 new SomeDataService() 基本相同),所以我可以尝试模拟具体类型 SomeDataService并覆盖我想要测试的方法,但是当具体类型具有副作用时,这会成为一个问题,例如 ChannelFactory,它在实例化后立即尝试解析有效​​的 WCF 绑定(bind),并在失败时抛出异常。我可以通过为它提供有效的绑定(bind)来避免这种失败,但我认为单元测试不应该依赖于这些东西。

有什么建议吗?我的一个想法如下:

void Initialize()
{
if (_container.IsRegistered<ISomeDataService>())
   {
   someDataService = _container.Resolve<ISomeDataService>();
   }
else
   {
   someDataService = _container.Resolve<SomeDataService>(); // or new SomeDataService()
   }

_container.RegisterInstance<ISomeDataService>(someDataService);
someDataService.Connect();
}

通过这种方式我可以模拟 ISomeDataService 而不是具体类型 SomeDataService 并且一切都很好,但我不知道这是否是正确的方法......我确定我做错了并且必须有其他方式。

最佳答案

这是一个有趣的问题。

查看提供的示例,实际上测试了三件事:

  • 初始化注册你的服务
  • ISomeDataService 调用连接
  • SomeDataService 已正确实例化。

通常,我会将 Connect 推迟到稍后的某个时间点,因为这类似于在构造函数中进行工作,并且它表明该模块正在做不止一件事。如果您要删除 Connect 方法,这将很容易测试。但是,您的需求可能会有所不同,所以我离题了......

这三件事中的每一件事都应该是单独的测试。诀窍是找到合适的“接缝”来将实例化与注册分离,并用模拟替换服务,以便我们可以验证 Connect 方法。

下面是对上面的一个小改动:

public void Initialize()
{
    ISomeDataService service = DataService;
    service.Connect();
    _container.RegisterInstance<ISomeDataService>(service);
}

internal ISomeDataService DataService
{
  get { return _service ?? _service = _container.Resolve<SomeDataService>(); }
  set { _service = value;}
}

或者,您可以使用子类来测试模式:

protected internal virtual ISomeDataService GetDataService()
{
  return _container.Resolve<SomeDataService>();
}

上面几个有趣的点:

  1. 您可以通过为被测对象分配模拟服务来测试注册,调用 Initialize,然后尝试从容器中手动解析服务。断言已解析的服务与您的模拟实例相同。

  2. 您可以通过分配模拟来测试 Connect,调用 Initialize,然后验证是否调用了 Connect。

  3. 您可以通过使用适当的依赖项填充容器并从 DataService 属性或基 GetDataService() 检索实例(如果您使用的是要测试的子类)来测试服务是否可以实例化。

最后一个是您的争论点。您不想为测试添加 wcf 配置。我同意,但是因为我们在前两个测试中解耦了模块的行为,所以只需要最后一个测试的配置。最后一个测试是一个集成测试,证明你有合适的配置文件;我会用 Integration 类别属性标记此测试,并与其他测试一起运行它,这些测试使用适当的配置加载和初始化所有模块。毕竟,关键是要验证它是否一切正常——诀窍是为孤立的组件获得有意义的反馈。

最后一点,您问题中显示的代码建议您通过用模拟填充来测试主题。这与我在这里提出的非常相似,但主要区别在于语义:模拟是主体职责的一部分,它不是通过容器注入(inject)的依赖项。以这种方式编写它很清楚什么是模块的一部分以及什么是必需的依赖项。

希望这有助于...

关于c# - 单元测试组合根?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13976760/

相关文章:

c# - 如何检索 .Net WinForms 图表控件中的选定范围?

java - 如何在java中对同步方法进行单元测试?

c# - IoC 和构造函数过度注入(inject)反模式解决方案

python - 模拟通过实例使用的类方法

android - 如何将 Activity 注入(inject)另一个类(class)

c# - 如何使用 CaSTLe Windsor 和 MS Test 将依赖项传递给对象?

c# - FileHelper 读取以使用 web api 流式传输和下载

c# - 通过 C# 代码管理 DNS 服务器

c# - 用于单元测试的 Azure C# EventHubClient 模拟

c# - Assert.ThrowsExceptionAsync 不工作