【问题标题】:How to implement Factory for production code, dependency injection for Unit Tests如何为生产代码实现工厂,为单元测试实现依赖注入
【发布时间】:2018-04-19 05:49:03
【问题描述】:

我想让我的类的调用者能够按名称选择提供者,而不是像标准 DI 推荐的那样传递提供者具体类。它将允许对客户端隐藏实际的实现细节,仍然可以控制使用哪个提供者。我们通过实现工厂来做到这一点

public ICurrencyProvider GetCurrencyServiceProvider(string providerName)
    {
        switch (providerName)
        {
            case "CurrencyLayerAPI":
                {  currencyService = new CurrencyLayerWrapper(); }
                break;
            case "XE":
                { currencyProvider = new XEWrapper(); }
                break;
            }
        return _currencyProvider;
    }

并且构造函数期望 providerName 作为参数。

但是对于单元测试,我希望使用替代,而不是提供者的具体类。 我最终得到了 2 个参数,它们负责相同的选择——生产代码的名称和测试调用的接口。

    public CurrencyProcessor(string providerName, ICurrencyProvider substituteCurrencyProvider =null)
   {
          if(!providerName .IsNullOrEmpty()) 
          {         
             _currencyProvider = GetCurrencyServiceProvider(providerName);
            }
          else
          {  _currencyProvider =substituteCurrencyProvider; 
          }
    }

另一种实现方式是从配置中读取 providerName,而不是将其作为参数传递。

public CurrencyProcessor(IConfigurationProvider configurationProvider,  ICurrencyProvider substituteCurrencyProvider =null)
 {
     _providerName = _configurationProvider.GetAppSetting("CurrencyProviderToUse"); 
      if(!providerName .IsNullOrEmpty()) 
      {         
         _currencyProvider = GetCurrencyServiceProvider(providerName);
      }
      else
      {  _currencyProvider =substituteCurrencyProvider;
      }
}

我徘徊,是否存在更好的方法来使用单个参数来控制内部对象的创建,但避免将创建对象的责任交给客户端。

相关讨论 How to use Dependency Injection without breaking encapsulation?
Preferable way of making code testable: Dependency injection vs encapsulation
https://softwareengineering.stackexchange.com/questions/344442/dependency-injection-with-default-construction

【问题讨论】:

  • 你要么必须注入工厂,要么注入ICurrencyProvider 而不是你在这里做的事情
  • 鉴于您的构造函数正在调用工厂,我建议您只注入 ICurrencyProvider
  • @KeithNicholas:注入 ICurrencyProvider 期望调用者负责创建具体的 CurrencyProvider 实现,我希望避免这种情况。不确定如何注入工厂?
  • 它已经通过传递一个“字符串”来负责,一个表示类型的字符串,最好使用类型

标签: c# unit-testing dependency-injection encapsulation factory-pattern


【解决方案1】:

你真正需要和你的工厂一起应用的是策略模式

interface ICurrencyProvider {
    //...members
}

public interface ICurrencyProviderStrategy {
    string Name { get; }
    ICurrencyProvider Create();
}

public interface ICurrencyProviderFactory {
    ICurrencyProvider GetCurrencyServiceProvider(string providerName);
}

工厂的实现将依赖于调用一系列策略来创建所需的类型。

public class CurrencyProviderFactory : ICurrencyProviderFactory {
    private readonly IEnumerable<ICurrencyProviderStrategy> strategies;

    public CurrencyProviderFactory(IEnumerable<ICurrencyProviderStrategy> strategies) {
        this.strategies = strategies;
    }

    public ICurrencyProvider GetCurrencyServiceProvider(string providerName) {
        var provider = strategies.FirstOrDefault(p => p.Name == providerName);
        if (provider != null)
            return provider.Create();
        return null;
    }
}

这将提供更大的灵活性,因为可以注入任意数量的策略。

这是CurrencyLayerWrapper 策略的示例

public class CurrencyLayerWrapperProvider : ICurrencyProviderStrategy {
    public string Name { get { return "CurrencyLayerAPI"; } }
    public ICurrencyProvider Create() {
        return new CurrencyLayerWrapper();
    }
}

【讨论】:

【解决方案2】:

由于您在构造函数中静态创建提供程序,因此只需注入提供程序即可。

按照你的描述创建一个工厂......

 public class CurrencyFactory
    {
        public static ICurrencyProvider  GetCurrencyServiceProvider(string providerName)
        {
            return null;
        }
    }

然后使用标准依赖注入:-

public class CurrencyProcessor
{
    private ICurrencyProvider _currencyProvider;

    public CurrencyProcessor(ICurrencyProvider currencyProvider)
    {
        _currencyProvider = currencyProvider;
    }
}

然后像这样使用

var p = new CurrencyProcessor(CurrencyFactory.GetCurrencyServiceProvider("bitcoin"));

然后在你的测试中模拟它

var mock = new Mock<ICurrencyProvider>(). // mock stuff

【讨论】:

    【解决方案3】:

    不确定我是否理解正确。

    对我来说,听起来你想要拥有 2 个不同的工厂。

    首先创建一个接口:

    public interface ICurrencyProviderFactory
    {
        ICurrencyProvider Create()
    }
    

    然后创建一个配置工厂:

    public class ConfigurationCurrencyProviderFactory : ICurrencyProviderFactory
    {
        public ConfigurationCurrencyProviderFactory(IConfigurationProvider configuration)
        {    
        }
    
        public ICurrencyProvider Create()
        {
        }    
    }
    

    然后是 UnitTest 工厂:

    public class UnitTestCurrencyProviderFactory : ICurrencyProviderFactory
    {
        public UnitTestCurrencyProviderFactory()
        {    
        }
    
        public ICurrencyProvider Create()
        {
        }    
    }
    

    您的货币处理器应如下所示:

    public CurrencyProcessor(ICurrencyProviderFactory factory)
    {
        _currencyProvider = factory.Create();
    }
    

    在您的 ServiceCollection 或您解决依赖项的任何地方,您都应该包含正确的工厂。

    因此,对于生产,您添加 ConfigurationCurrencyProviderFactory,对于 UnitTest,添加 UnitTestCurrencyProviderFactory。那么你的实际代码应该取决于ICurrencyProviderFactory

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      • 1970-01-01
      • 1970-01-01
      • 2018-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多