Есть несколько проблем с тем, как вы разработали свое приложение.Прежде всего, вы вызываете ядро Ninject прямо из своего кода.Это называется шаблон локатора службы , а считается анти-шаблоном .Это делает тестирование вашего приложения намного сложнее, и вы уже испытываете это.Вы пытаетесь смоделировать контейнер 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
:
interface IDirEnumFactory
{
IDirEnum CreateDirEnum(string filePath, string fileFilter,
bool includeSubDirs);
}
Ваш класс 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
.Это называется Шаблон внедрения зависимостей , в отличие от шаблона Service Locator, где Configurator
запрашивает свои зависимости, вызывая ядро Ninject.
Теперь, так как ваш Configurator
он полностью свободен от любого контейнера IoC, и теперь вы можете легко протестировать этот класс, внедрив макетированную версию ожидаемой зависимости.
Осталось настроить контейнер Ninject в верхней части окна.приложение (в терминологии DI: составной корень ).В примере внедрения метода ваша конфигурация контейнера останется прежней, а в заводском примере вам потребуется заменить строку 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);
}
}
ПРЕДУПРЕЖДЕНИЕ : обратите внимание, что заводские абстракции в большинстве случаев не лучший дизайн, как объяснено здесь .
Последнее, что вам нужно сделать, - это создать новый экземпляр 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
.Когда вы сохраняете свои конструкторы чистыми от какой-либо логики и используете ее только для хранения взятых зависимостей в частных полях, вы можете запускать это без риска вызова базы данных, веб-службы или чего-либо подобного.
Надеюсь, это поможет.