您设计应用程序的方式存在一些问题。首先,您直接从代码中调用 Ninject 内核。这被称为服务定位器模式 http://en.wikipedia.org/wiki/Service_locator_pattern and 它被认为是一种反模式 http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx。它使测试您的应用程序变得更加困难,并且您已经经历过这一点。您试图在单元测试中模拟 Ninject 容器,这使事情变得非常复杂。
接下来,您将注入原始类型(string
, bool
)在你的构造函数中DirEnum
类型。我喜欢 MNrydengren 在评论中的表述:
获取“编译时”依赖项
通过构造函数参数和
通过方法的“运行时”依赖关系
参数
我很难猜测该类应该做什么,但是由于您将这些在运行时更改的变量注入到DirEnum
构造函数,您最终会得到一个难以测试的应用程序。
有多种方法可以解决这个问题。我想到的两个是方法注入的使用和工厂的使用。哪一种可行取决于您。
使用方法注入,你的Configurator
类将如下所示:
class Configurator
{
private readonly IDirEnum dirEnum;
// Injecting IDirEnum through the constructor
public Configurator(IDirEnum dirEnum)
{
this.dirEnum = dirEnum;
}
public ConfigureServices(string[] args)
{
var parser = new ArgParser(args);
// Inject the arguments into a method
this.dirEnum.SomeOperation(
argParser.filePath
argParser.fileFilter
argParser.subDirs);
}
}
使用工厂,您需要定义一个知道如何创建新的工厂IDirEnum
types:
interface IDirEnumFactory
{
IDirEnum CreateDirEnum(string filePath, string fileFilter,
bool includeSubDirs);
}
Your Configuration
类现在可以依赖于IDirEnumFactory
界面:
class Configurator
{
private readonly IDirEnumFactory dirFactory;
// Injecting the factory through the constructor
public Configurator(IDirEnumFactory dirFactory)
{
this.dirFactory = dirFactory;
}
public ConfigureServices(string[] args)
{
var parser = new ArgParser(args);
// Creating a new IDirEnum using the factory
var dirEnum = this.dirFactory.CreateDirEnum(
parser.filePath
parser.fileFilter
parser.subDirs);
}
}
查看这两个示例中如何将依赖项注入到Configurator
班级。这被称为依赖注入模式 http://en.wikipedia.org/wiki/Dependency_injection,与服务定位器模式相反,其中Configurator
通过调用 Ninject 内核来请求其依赖项。
现在,自从你的Configurator
完全不受任何 IoC 容器的影响,您现在可以通过注入所需依赖项的模拟版本来轻松测试此类。
剩下的就是在应用程序顶部配置 Ninject 容器(用 DI 术语来说:成分根 https://stackoverflow.com/questions/6277771/what-is-a-composition-root-in-the-context-of-dependency-injection)。对于方法注入示例,您的容器配置将保持不变,对于工厂示例,您将需要替换Bind<IDirEnum>().To<DirEnum>()
符合以下内容:
public static void Bootstrap()
{
kernel.Bind<IDirEnumFactory>().To<DirEnumFactory>();
}
当然,您需要创建DirEnumFactory
:
class DirEnumFactory : IDirEnumFactory
{
IDirEnum CreateDirEnum(string filePath, string fileFilter,
bool includeSubDirs)
{
return new DirEnum(filePath, fileFilter, includeSubDirs);
}
}
WARNING:请注意,工厂抽象在大多数情况下并不是最好的设计,正如所解释的那样here https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=100.
您需要做的最后一件事是创建一个新的Configurator
实例。您可以简单地执行以下操作:
public static Configurator CreateConfigurator()
{
return kernel.Get<Configurator>();
}
public static void Main(string[] args)
{
Bootstrap():
var configurator = CreateConfigurator();
configurator.ConfigureServices(args);
}
这里我们称之为内核。尽管应该防止直接调用容器,但应用程序中始终至少有一个地方可以调用容器,因为它必须连接所有内容。然而,我们尝试最小化直接调用容器的次数,因为它提高了代码的可测试性。
看看我如何没有真正回答你的问题,但展示了一种非常有效地解决问题的方法。
您可能仍想测试您的 DI 配置。在我看来,这是非常有效的。我在我的应用程序中这样做。但为此,您通常不需要 DI 容器,或者即使您需要,这并不意味着您的所有测试都应该依赖于该容器。这种关系只应存在于测试 DI 配置本身的测试中。这是一个测试:
[TestMethod]
public void DependencyConfiguration_IsConfiguredCorrectly()
{
// Arrange
Program.Bootstrap();
// Act
var configurator = Program.CreateConfigurator();
// Assert
Assert.IsNotNull(configurator);
}
该测试间接依赖于 Ninject,当 Ninject 无法构造新的测试时,该测试将会失败Configurator
实例。当您保持构造函数不包含任何逻辑并且仅将其用于将获取的依赖项存储在私有字段中时,您可以运行它,而无需承担调用数据库、Web 服务或其他任何内容的风险。
我希望这有帮助。