Ninject переопределения привязки ядра - PullRequest
5 голосов
/ 11 августа 2009

Мне просто интересно, как лучше переписать привязки в ядре.

У меня есть класс с ядром и модуль частного класса с производственными привязками по умолчанию.

Для тестов я хочу переопределить эти привязки, чтобы я мог поменять их объектами Test Doubles / Mocks.

делает

MyClass.Kernel.Load(new InlineModule(m=> m.Bind<IDepend>().To<TestDoubleDepend>()))

переопределить любые существующие привязки для IDepend?

Ответы [ 6 ]

5 голосов
/ 12 августа 2009

Я стараюсь использовать ядро ​​DI непосредственно в своем коде как можно меньше, вместо этого полагаясь на инжектор конструктора (или свойства в некоторых случаях, таких как Attribute классы). Однако там, где я должен, я использую уровень абстракции, чтобы я мог установить объект ядра DI, делая его пригодным для использования в модульных тестах.

Например:

public interface IDependencyResolver : IDisposable
{
    T GetImplementationOf<T>();
}

public static class DependencyResolver
{
    private static IDependencyResolver s_resolver;

    public static T GetImplementationOf<T>()
    {
        return s_resolver.GetImplementationOf<T>();
    }

    public static void RegisterResolver( IDependencyResolver resolver )
    {
        s_resolver = resolver;
    }

    public static void DisposeResolver()
    {
        s_resolver.Dispose();
    }
}

Используя такой шаблон, вы можете установить IDependencyResolver из модульных тестов, вызвав RegisterResolver с фиктивной или поддельной реализацией, которая возвращает любые объекты, которые вы хотите, без необходимости подключения полных модулей. Это также имеет дополнительное преимущество - абстрагирование вашего кода от конкретного контейнера IoC, если вы решите в будущем переключиться на другой.

Естественно, вы также захотите добавить дополнительные методы к IDependencyResolver, как того требуют ваши потребности, я просто привожу здесь основы в качестве примера. Да, тогда потребуется написать супер простую оболочку вокруг ядра Ninject, которая также реализует IDependencyResolver.

Причина, по которой вы хотите это сделать, заключается в том, что ваши модульные тесты должны на самом деле тестировать только одну вещь, и, используя ваш реальный контейнер IoC, вы действительно выполняете больше, чем один тестируемый класс, что может привести к ложным негативам, которые сделать ваши тесты хрупкими и (что более важно) поколебать веру разработчиков в их точность с течением времени. Это может привести к апатии и отказу от тестов, так как становится возможным провал тестов, но программное обеспечение по-прежнему работает правильно («не волнуйтесь, это всегда сбой, это не имеет большого значения»).

3 голосов
/ 28 декабря 2011

Я просто надеюсь, что что-то подобное работает

        var kernel = new StandardKernel(new ProductionModule(config));
        kernel.Rebind<ITimer>().To<TimerImpl>().InSingletonScope();

где ProductionModule - мои производственные привязки, и я перезаписываю, вызывая Rebind в конкретном тестовом примере. Я перезваниваю по нескольким предметам, которые перепривязываю.

ADVANTAGE: Если кто-то добавляет новые привязки к производственному модулю, я наследую их, чтобы это не сломалось, что может быть приятно. Это все работает в Guice в Java ... надеюсь, что это работает и здесь.

1 голос
/ 17 августа 2009

Подход Питера Мейера может быть полезен для модульного теста, но, имхо, не проще ли просто ввести Mock вручную, используя конструктор / внедрение свойства?

Мне кажется, что использование конкретных привязок для тестового проекта будет более полезным для других видов испытаний (интеграционных, функциональных), но даже в этом случае вам обязательно нужно изменить привязки в зависимости от теста.

Мой подход - это своего рода смесь kronhrbaugh и Hamish Smith, создающая «средство разрешения зависимостей», где вы можете регистрировать и отменять регистрацию используемых модулей.

1 голос
/ 12 августа 2009

То, что я склонен делать, - это иметь отдельный тестовый проект со своими привязками - я, конечно, предполагаю, что мы говорим о каких-то модульных тестах. Тестовый проект использует свое собственное ядро ​​и загружает модуль в тестовом проекте в это ядро. Тесты в проекте выполняются во время сборок CI и полными сборками, выполняемыми из скрипта сборки, хотя тесты никогда не развертываются в рабочей среде.

Я понимаю, что настройка вашего проекта / решения может не разрешить такого рода организацию, но, похоже, это довольно типично из того, что я видел.

0 голосов
/ 28 августа 2009

Для проекта, над которым я работаю, я создал отдельные модули для каждой среды (тестирование, разработка, этап, производство и т. Д.). Эти модули определяют привязки.

Поскольку dev, stage и production используют много одинаковых привязок, я создал общий модуль, из которого все они получены. Затем каждый из них добавляет привязки для конкретной среды.

У меня также есть KernelFactory, который при передаче токена среды будет спулинговать IKernel с соответствующими модулями.

Это позволяет мне переключать токен окружения, который в свою очередь автоматически изменит все мои привязки.

Но если это для модульного тестирования, я согласен с приведенными выше комментариями, что простой конструктор, допускающий ручное связывание, - это путь, который не позволяет Ninject оставаться в ваших тестах.

0 голосов
/ 12 августа 2009

Я бы добавил конструктор в MyClass, который принимает модуль.
Это не будет использоваться в производстве, но будет использоваться в тесте.
В тестовом коде я передал бы модуль, который определил требуемые контрольные двойники.

...