假设我在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而不是新的Fruit作为:
public class AppleTree
{
private readonly IFruitFactory _fruitFactory;
public AppleTree(IFruitFactory fruitFactory)
{
_fruitFactory = fruitFactory
}
public string GetApple
{
return _fruitFactory.CreateApple().ToString();
}
}
现在我的问题是:有没有一个很好的方法来做到这一点,而无需创建工厂?例如,我可以以某种方式使用像Ninject这样的依赖注入器吗?
答案 0 :(得分:1)
您可以模拟已实现的类型。我所知道的产品做得非常好Typemock。微软有一个名为Moles的产品,但我还没有遇到任何实际使用它的人。我没有太多的输入它的用处。
您还可以向运行时注册COM接口以拦截对象创建以注入您自己的对象。这就是这些产品的工作原理。这些是获得所需内容的最少代码更改。
长期以来,我倾向于用工厂等方式抽出Fruit。但是像Typemock或Moles这样的工具可以在短期内让你更快。如果一切都在界面后面,那么前进的摩擦就会减少。
答案 1 :(得分:1)
也许,最简单的是,使用虚方法/属性实现自己的抽象 FruitBase 类。然后进行实现,包含真正的 Fruit 类 - FruitWrapper 。与 System.Web 中 HttpContextBase , HttpContextWrapper 和 HttpContext 的逻辑相同。
然后,您可以轻松地模拟所有内容,而无需任何特定于供应商的黑暗魔法测试框架。
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")
。
答案 2 :(得分:0)
您通常会Fruit
实现IFruit
接口而不是工厂,而是注入该接口:
public class AppleTree
{
private readonly IFruit _apple;
public AppleTree(IFruit apple)
{
_apple = apple;
}
public string GetApple()
{
return _apple.ToString();
}
}
通过这种实现,您可以实例化:
new AppleTree(new Fruit("Apple"))
或者您可以拥有像NInject或StructureMap这样的IoC容器为您做这件事new AppleTree(mockApple)
其中mockApple
是您为单元测试创建的模拟IFruit对象答案 3 :(得分: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());
}
}
}
}
答案 4 :(得分:0)
请参阅Ninject.Extensions.Factory
- 有了它,你有Func<T>
个ctor参数,抽象工厂是在幕后生成的(la @ Nenad的+1回答)
(您还可以定义Abstract Factory界面并Ninject.Extensions.Factory实施该界面。