Какой подход подходит для проверки привязок Ninject?

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

Итак, я спрашиваю, как я могу знать, что мое ядро ​​после загрузки всех модулей и привязок сможет разрешать каждый тип? Вы делаете какой-либо юнит-тест? Или вы просто делаете приемочные тесты приложения во время выполнения? Любое предложение будет отличным:)

2 ответа

Решение

Напишите интеграционный тест, который проверяет конфигурацию контейнера, перебирая все корневые типы в приложении и запрашивая их из контейнера / ядра. Запрашивая их из контейнера, вы уверены, что контейнер может построить полный граф объектов для вас.

Корневой тип - это тип, который запрашивается непосредственно из контейнера. Большинство типов не будут корневыми типами, а являются частью графа объектов (так как вам редко придется перезванивать в контейнер из приложения). Когда вы тестируете создание корневого типа, вы сразу же тестируете создание всех зависимостей этого корневого типа, если только нет прокси, фабрик или других механизмов, которые могут задержать процесс построения. Однако механизмы, которые задерживают процесс построения, указывают на другие корневые объекты. Вы должны идентифицировать их и проверить их создание.

Защитите себя от одного огромного теста с вызовом контейнера для каждого корневого типа. Вместо этого загрузите (если возможно) все корневые типы, используя отражение, и итерируйте их. Используя какое-то соглашение о подходе к конфигурации, вы избавляете себя от необходимости: 1. менять тест для каждого нового корневого типа и 2. предотвращать неполный тест, если вы забыли добавить тест для нового корневого типа.

Вот пример для ASP.NET MVC, где ваши корневые типы являются контроллерами:

[TestMethod]
public void CompositionRoot_IntegrationTest()
{
    // Arrange
    CompositionRoot.Bootstrap();

    var mvcAssembly = typeof(HomeController).Assembly;

    var controllerTypes =
        from type in mvcAssembly.GetExportedTypes()
        where typeof(IController).IsAssignableFrom(type)
        where !type.IsAbstract
        where !type.IsGenericTypeDefinition
        where type.Name.EndsWith("Controller")
        select type;

    // Act
    foreach (var controllerType in controllerTypes)
    {
        CompositionRoot.GetInstance(controllerType);
    }
}

ОБНОВИТЬ

Себастьян Вебер сделал интересный комментарий, на который мне нравится отвечать.

А как насчет отложенного создания объекта с использованием фабрики на основе контейнера (Func) или фабрики, сгенерированной контейнером (например, Castle's Typed Factory Facility)? Вы не поймаете их с таким тестом. Это даст вам ложное чувство безопасности.

Мой совет о проверке всех корневых типов. Службы, созданные с задержкой, на самом деле являются корневыми типами, и поэтому их следует тестировать явно. Это, конечно, вынуждает вас внимательно следить за изменениями в вашей конфигурации и добавлять тест, когда вы обнаруживаете, что добавлен новый корневой тип, который не может быть протестирован с помощью уже существующих тестов с переконфигурацией. Это неплохо, так как никто не сказал, что использование DI и контейнера DI означает, что мы можем внезапно стать небрежными. Создание хорошего программного обеспечения требует дисциплины, используете ли вы DI или нет.

Конечно, такой подход станет довольно неудобным, если у вас есть много регистраций, которые делают отложенное создание. В этом случае, вероятно, что-то не так с дизайном вашего приложения, поскольку использование отложенного создания должно быть исключением, а не нормой. Еще одна вещь, которая может привести к неприятностям, - это когда ваш контейнер позволяет разрешить незарегистрированные Func<T> регистрации, сопоставляя их с () => container.GetInstance<T>() делегировать. Это звучит хорошо, но это заставляет вас смотреть за пределы регистрации контейнера, чтобы искать корневые типы, и значительно упускает возможность пропустить один. Поскольку использование отложенного создания должно быть исключением, вам лучше с явной регистрацией.

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

Итак, я спрашиваю, как я могу знать, что мое ядро ​​после загрузки всех модулей и привязок сможет разрешать каждый тип? Вы делаете какой-либо юнит-тест?

Я проверяю это, перебирая все привязки в моём модуле (модулях) и проверяя, что ядро ​​может что-то вернуть для них:

[Test]
public void AllModuleBindingsTest()
{
    var kernel = new StandardKernel(new MyNinjectModule())
    foreach (var binding in new MyNinjectModule().Bindings)
    {
        var result = kernel.Get(binding.Service);
        Assert.NotNull(result, $"Could not get {binding.Service}");
    }
}

На основании ответа Оуэна, но это не сработало для меня как есть. то, что я должен был сделать, это передать новый экземпляр моих зависимостей в новый экземпляр ядра, и в этот момент Bindings собственность была заселена.

    [TestMethod]
    public void TestBindings
    {
        var dependencies = new MyDependencies();
        var kernel = new StandardKernel(dependencies);

        foreach (var binding in dependencies.Bindings)
        {
            kernel.Get(binding.Service);
        }
    }

Также я решил использовать kernal.Get так что если служба не может быть разрешена, то тест не пройден из-за возникшей ошибки, а также отобразит отсутствующие привязки в сообщении

Другие вопросы по тегам