【问题标题】:Injecting third party classes without creating factories在不创建工厂的情况下注入第三方类
【发布时间】:2012-10-21 12:25:41
【问题描述】:

假设我在 C# 中有以下代码:

public class AppleTree
{
   public AppleTree()
   {
   }

   public string GetApple
   {
      return new Fruit("Apple").ToString();
   }
}

Fruit 是没有接口的第三方类。

我想为 AppleTree 类创建一个单元测试,但我不想运行 Fruit 类。相反,我想注入 Fruit 类,以便在测试中模拟它。

我该怎么做呢?我可以创建一个创建苹果的工厂,然后向该工厂添加一个接口,例如:

public class FruitFactory : IFruitFactory
{
   Fruit CreateApple()
   {
      return new Fruit("Apple");
   }
}

现在我可以将 IFruitFactory 注入 AppleTree 并使用 CreateApple 而不是 new Fruit 作为:

public class AppleTree
{
   private readonly IFruitFactory _fruitFactory;

   public AppleTree(IFruitFactory fruitFactory)
   {
      _fruitFactory = fruitFactory
   }

   public string GetApple
   {
      return  _fruitFactory.CreateApple().ToString();
   }
}

现在我的问题是:有没有一种无需创建工厂就可以做到这一点的好方法?例如,我可以以某种方式使用像 Ninject 这样的依赖注入器吗?

【问题讨论】:

    标签: c# dependency-injection ninject


    【解决方案1】:

    也许,最简单的是,用虚拟方法/属性实现您自己的抽象 FruitBase 类。然后实现包装真正的 Fruit 类 - FruitWrapper。与 System.Web 中的 HttpContextBaseHttpContextWrapperHttpContext 的逻辑相同。

    然后您可以轻松地模拟所有内容,而无需任何供应商特定的暗魔法测试框架。

    public class AppleTree
    {
        private readonly Func<FruitBase> _constructor;
        public AppleTree(Func<FruitBase> constructor)
        {
            _constructor = constructor;
        }
    
        public string GetApple()
        {
            return new _constructor().ToString();
        }
    }
    

    您可以在构造函数中交换函数:() =&gt; new FruitMock()() =&gt; new Fruit("Apple")

    【讨论】:

      【解决方案2】:

      您可以模拟已实现的类型。我所知道的能很好地做到这一点的产品是Typemock。微软有一个名为Moles 的产品,但我还没有遇到真正使用它的人。我对它的用处没有太多意见。

      您还可以向运行时注册 COM 接口以拦截对象创建以注入您自己的。这就是这些产品的工作原理。这些是获得所需位置的最少代码更改量。

      从长远来看,我倾向于通过工厂或类似的方式将 Fruit 抽象出来。但是像 Typemock 或 Moles 这样的工具会在短期内让你更快地到达那里。如果一切都在接口后面,那么前进的摩擦就会减少。

      【讨论】:

        【解决方案3】:

        参见Ninject.Extensions.Factory - 有了它,您就有了Func&lt;T&gt; ctor 参数,并且在幕后生成了抽象工厂(@Nenad 的 +1 答案)

        (您也可以定义一个Abstract Factory 接口并让Ninject.Extensions.Factory 实现它。

        【讨论】:

        • 有趣的解决方案,我会尝试一下。作者有一篇博文对此进行了解释here
        • 祝你好运。 (顺便说一句,博客文章是我链接到的 wiki 文章的源材料 - 请记住它是一个 wiki,所以任何让你感到困惑或你有任何好主意的东西,去编辑吧!)
        【解决方案4】:

        您通常会让Fruit 实现IFruit 接口并注入它而不是工厂:

        public class AppleTree
        {
            private readonly IFruit _apple;
        
            public AppleTree(IFruit apple)
            {
                _apple = apple;
            }
        
            public string GetApple()
            {
                return _apple.ToString();
            }
        }
        

        通过这种实现,您可以实例化:

        1. new AppleTree(new Fruit("Apple")) 或者您可以使用 NInject 或 StructureMap 之类的 IoC 容器为您完成这项工作
        2. new AppleTree(mockApple) 其中mockApple 是您为单元测试创​​建的模拟 IFruit 对象

        【讨论】:

          【解决方案5】:

          如果你不能编程到 IFruit 接口,那么我认为抽象工厂是要走的路。

          关于单元测试,如果您使用的是 Visual Studio 2012,您可以尝试Microsoft Fakes。以下代码只是为了好玩(它当然有效)。也许这对你的情况来说太过分了。

          using Microsoft.QualityTools.Testing.Fakes;
          using MyLib;
          
          namespace UnitTestProject1
          {
              [TestClass]
              public class UnitTest1
              {
                  [TestMethod]
                  public void TestMethod1()
                  {
                      using (ShimsContext.Create())
                      {
                          MyLib.Fakes.ShimFruit.ConstructorString = delegate(Fruit f, string s)
                          {
                              var shimFruit = new MyLib.Fakes.ShimFruit(f);
                              shimFruit.ToString = () =>
                              {
                                  return "Orange";
                              };
                          };
                          AppleTree tree = new AppleTree();
                          string expected = "Orange";
                          Assert.AreEqual(expected, tree.GetApple());
                      }
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2019-01-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-02-04
            • 1970-01-01
            • 2012-05-01
            • 1970-01-01
            • 2019-11-06
            相关资源
            最近更新 更多