2009-08-11 7 views
5

Me pregunto cuál es la mejor práctica para volver a cablear los enlaces en un kernel.El enlace del núcleo de Ninject anula

Tengo una clase con un kernel y un módulo de clase privado con los enlaces de producción predeterminados.

Para las pruebas, quiero anular estos enlaces para poder intercambiar mis objetos de Prueba Dobles/Simulacros.

hace

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

neutralicen alguna de las consolidaciones existentes para IDepend?

Respuesta

0

Agregaría un constructor a MyClass que acepta un Módulo.
Esto no se usaría en producción pero se usaría en la prueba.
En el código de prueba, aprobaría un Módulo que definía los dobles de prueba requeridos.

1

Lo que tiendo a hacer es tener un proyecto de prueba por separado completo con sus propias fijaciones. Por supuesto, supongo que estamos hablando de pruebas unitarias de algún tipo. El proyecto de prueba usa su propio kernel y carga el módulo en el proyecto de prueba en ese kernel. Las pruebas en el proyecto se ejecutan durante las compilaciones de CI y en compilaciones completas ejecutadas desde un script de compilación, aunque las pruebas nunca se implementan en producción.

Me doy cuenta de que su configuración de proyecto/solución puede no permitir este tipo de organización, pero parece ser bastante típica de lo que he visto.

5

Intento utilizar el kernel DI directamente en mi código lo menos posible, en lugar de confiar en la inyección del constructor (o propiedades en casos seleccionados, como las clases Attribute). Donde debo, sin embargo, uso una capa de abstracción, de modo que pueda establecer el objeto del núcleo DI, haciéndolo divisible en pruebas unitarias.

Por ejemplo:

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(); 
    } 
} 

El uso de un patrón de esta manera, se puede establecer el IDependencyResolver de pruebas unitarias llamando RegisterResolver con una aplicación simulada o falsa que devuelve cualquier objeto que desee sin tener que cablear módulos completos . También tiene un beneficio secundario de abstraer su código de un contenedor de IoC particular, si elige cambiar a uno diferente en el futuro.

Naturalmente, también le gustaría agregar métodos adicionales al IDependencyResolver según sus necesidades, solo estoy incluyendo aquí lo básico. Sí, esto requeriría que escribas un envoltorio super simple alrededor del kernel Ninject que implemente IDependencyResolver también.

La razón por la que desea hacer esto es que las pruebas de su unidad solo deberían probar una cosa y utilizando su contenedor IoC real, está realmente haciendo más ejercicio que la clase bajo prueba, lo que puede generar falsos negativos. que hacen que tus pruebas sean frágiles y (lo que es más importante) sacuden la confianza del desarrollador en su precisión a lo largo del tiempo. Esto puede llevar a la apatía y al abandono de la prueba, ya que es posible que las pruebas fallen pero el software funcione correctamente ("no se preocupe, siempre falla, no es gran cosa").

+0

+1 para el patrón de resolver, sin embargo, REALMENTE intente utilizar la inyección de constructor en su MyClass que requiere IDepend. de esa manera, las pruebas de su unidad ni siquiera necesitan su contenedor IoC. –

+0

¿Por qué no hacer algo más simple como anular enlaces como ese ... var kernel = new StandardKernel (nuevo ProductionModule (config)); kernel.Rebind () .A () .InSingletonScope(); –

+0

Puede anular los enlaces, pero luego sus pruebas también están probando su contenedor DI, junto con la unidad bajo prueba. En algunas situaciones, esto no es un gran problema, pero puede llevar a falsos negativos y erosionar la fe en el conjunto de pruebas, como expliqué en mi respuesta. – krohrbaugh

1

El enfoque de Peter Mayer sería útil para la prueba de unidad, pero en mi humilde opinión, ¿no es más fácil simplemente inyectar manualmente un simulacro usando una inyección de constructor/propiedad?

Me parece que el uso de enlaces específicos para un proyecto de prueba será más útil para otro tipo de prueba (integración, funcional) pero incluso en ese caso seguramente necesitará cambiar los enlaces dependiendo de la prueba.

Mi enfoque es una especie de mezcla de Kronhrbaugh y Hamish Smith, creando un "resolvedor de dependencias" donde puede registrar y anular el registro de los módulos que se utilizarán.

+0

Estoy de acuerdo con usted. En la mayoría de mis pruebas unitarias, estoy haciendo una inyección "manual", lo que suena demasiado sofisticado para simplemente pasarle un simulacro a un constructor. He utilizado DI en pruebas unitarias en algunos casos aislados y específicos, pero ciertamente en una minoría de casos. –

0

Para un proyecto en el que estoy trabajando, creé módulos separados para cada entorno (prueba, desarrollo, etapa, producción, etc.). Esos módulos definen los enlaces.

Dado que el desarrollo, la etapa y la producción utilizan todos los mismos enlaces, creé un módulo común del que todos ellos derivan. Luego, cada uno lo agrega enlaces específicos del entorno.

También tengo un KernelFactory, que cuando se le entrega un token de entorno cargará un IKernel con los módulos apropiados.

Esto me permite cambiar mi token de entorno, que a su vez cambiará automáticamente todas mis vinculaciones.

Pero si esto es para pruebas unitarias, estoy de acuerdo con los comentarios anteriores de que un simple constructor que permite la encuadernación manual es el camino a seguir, ya que mantiene a Ninject fuera de sus pruebas.

3

sólo estoy esperando algo como esto funciona

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

donde ProductionModule es mis fijaciones de producción y que anulan llamando Rebind en el caso de la prueba específica. Llamo a rebind en los pocos elementos que vuelvo a vincular.

VENTAJA: Si alguien agrega nuevas vinculaciones al módulo de producción, las heredaré para que no se rompan de esta manera, lo que puede ser agradable. Todo esto funciona en Guice en Java ... esperando que funcione aquí también.

Cuestiones relacionadas