处理依赖注入范围中的标准库类型

时间:2017-06-19 15:42:50

标签: c# .net unit-testing dependency-injection

考虑以下类型:

class SomeType
{
    public void Configure()
    {
        var windowsService = new ServiceController("msdtc");
        windowsService.Start();
    }
}

至少有三个问题。

  1. 我们对ServiceController有隐含的依赖。

  2. 我们无法对Configure()进行单元测试。

  3. 我们有new运算符,违反了我们的DI策略。

  4. 为了解决这个问题,我们可以提取另一种类型并将其输入我们的SomeType

    interface IWindowsService
    {
        void Start();
    }
    
    class WindowsService : IWindowsService
    {
        private readonly ServiceController _serviceController;
        public WindowsService(string serviceName)
        {
            _serviceController = new ServiceController(serviceName));
        }
    
        public void Start() => _serviceController.Start();
    }
    
    class SomeType
    {
        private readonly IWindowsService _msdtcService;
        public SomeType(Func<string, IWindowsService> createServiceCallback) //explicit dependency
        {
            _msdtcService = createServiceCallback.Invoke("msdtc");
        }
    
        public void Configure() => _msdtcService.Start();
    }
    

    它解决了问题#1和#2,但我们在新类型new中仍然有一个WindowsService运算符。我试着理解我应该在DI容器中注册标准ServiceController还是直接使用它,如上所示(new)?

    container.RegisterType<ServiceController>();
    

    此外,我不确定我们是否应该尝试测试WindowsService ,或者最好将其重写为:

    class WindowsService : ServiceController, IWindowsService
    {
    }
    

    由于WindowsService现在只是继承,我们无法在此测试任何内容。该类型已经过Microsoft测试。 然而,它打破了封装,也许来自SOL I D的ISP。我们可以将IWindowsService投放到WindowsService甚至ServiceController

    处理标准稳定类型的最佳方法是什么? 如果有的话,请转介我另一个问题。 提前谢谢。

2 个答案:

答案 0 :(得分:3)

interface ISomeInterface
{
    void Configure();
}

class SomeType : ISomeInterface
{
    public void Configure()
    {
        var windowsService = new ServiceController("msdtc");
        windowsService.Start();
    }
}

我会像上面那样做。现在什么都不应该直接依赖于SomeType。一切都应该取决于ISomeInterface。这使得ServiceController的依赖性仅限于一个类。

new运算符确实不是问题。 IServiceController实现ServiceController没有SomeType,所以如果你想使用它,你必须自己绑定它。通过将其隐藏在实现接口的const char *p="people"; char *q="random"; while(*p) { *q=*p; *p++; *q++; } 中,至少你已经限制了有多少东西直接依赖它。

答案 1 :(得分:0)

您正在处理的问题是更大问题的子类型,即在IoC中处理系统级调用的问题。

问题的另一个例子是使用DateTime.Now。如果你在代码中调用它,则无法将其隔离,如果你想在各种时间场景中进行测试,这就是一个问题。

一种解决方案是取消所有系统级调用,以便您可以替换自己的模拟操作系统进行测试。 Here是DateTime的一个示例。我也将为您的具体问题提供一个示例:

interface IOperatingSystem
{
    void StartService(string name);
}

class OperatingSystem : IOperatingSystem
{
    public virtual void StartService(string name) {
        var windowsService = new ServiceController(name);
        windowsService.Start();
    }
}

class SomeType : ISomeType
{
    private readonly IOperatingSystem _operatingSystem;

    public SomeType(IOperatingSystem operatingSystem)
    {
        _operatingSystem = operatingSystem;
    }

    public void Configure()
    {
        _operatingSystem.StartService("msdtc");
    }
}

在您的IoC容器中:

container.RegisterType<IOperatingSystem, OperatingSystem>();
container.RegisterType<ISomeType, SomeType>();

现在,您可以通过覆盖操作系统类来隔离所有需要的内容:

class MockOperatingSystem : OperatingSystem
{
    public override StartService(string name)
    {
        //Do something worthy of testing, e.g. return normally or throw an exception
    }
}

并注册(在您的单元测试中),如下所示:

container.RegisterType<IOperatingSystem, MockOperatingSystem>();

现在,当您开始对此进行编码时,您可能会选择为不同的系统功能使用不同的接口(例如,您可能需要与其他O / S呼叫分开的IServiceControlManager)。这很好,也很常见。我更喜欢所有O / S呼叫的一个大班,因为我知道那些O / S呼叫不会改变。嗯,他们可能会改变,但如果他们这样做,我会遇到更大的问题,无论如何返工都是不可避免的。