Подтвердить что ты не робот

Что является хорошим подходом к тестированию привязок Ninject?

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

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

4b9b3361

Ответ 1

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

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

Предотвратите себя от одного огромного теста с вызовом контейнера для каждого типа корня. Вместо этого загрузите (если возможно) все корневые типы с использованием отражения и повторите их. Используя какой-то подход по сравнению с конфигурацией, вы избавляетесь от необходимости: 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);
    }
}

UPDATE

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

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

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

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

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

Ответ 2

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

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

[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}");
    }
}

Ответ 3

На основании ответа Оуэна, но это не сработало для меня как есть. мне нужно было передать новый экземпляр моих зависимостей в новый экземпляр ядра, и в этот момент было заполнено свойство 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 чтобы, если служба не могла быть разрешена, тест не kernal.Get ошибки, а также отобразил отсутствующие привязки в сообщении.