1

假设我在 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 这样的依赖注入器吗?

4

5 回答 5

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();
    }
}

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

于 2012-10-21T13:51:57.943 回答
1

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

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

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

于 2012-10-21T12:47:47.997 回答
0

如果你不能编程到 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());
            }
        }
    }
}
于 2012-10-21T17:28:52.343 回答
0

See Ninject.Extensions.Factory - with it, you have Func<T> ctor arguments and the Abstract Factory is generated behind the scenes (a la @Nenad's +1'd answer)

(You can also define an Abstract Factory interface and have Ninject.Extensions.Factory implement it.

于 2012-10-21T20:28:47.057 回答
0

而不是工厂,您通常会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 对象在哪里
于 2012-10-21T12:57:59.977 回答