11

Tengo una pregunta simple.Contenedor IoC, compruebe errores en tiempo de compilación

Digamos que tengo una solución .Net, con diferentes proyectos como algunas bibliotecas de clases (bll, dal, etc.) y un proyecto principal que puede ser una aplicación web o una aplicación wpf, no importa.

Ahora digamos que quiero usar un contenedor IoC (como Windsor, Ninject, Unity, etc.) para resolver cosas como validadores, repositorios, implementaciones de interfaz común y demás.

Lo puse todo junto. Compila y funciona bien. Luego, algún día, agrego un nuevo servicio, y en mi código solo trato de resolverlo a través del contenedor IoC. La cosa es que olvidé registrarlo en la configuración de IoC.

Todo se compila y la aplicación se implementa y se ejecuta. Todo funciona bien, excepto cuando el código de una página solicita ese nuevo servicio al contenedor y el contenedor responde "hey, no sé nada sobre este servicio".

Recibirá el error registrado y la página de error fácil de usar. Revisarás el error, verás el problema y lo arreglarás. Bastante estándar.

Digamos que queremos mejorar el proceso y, de alguna manera, saber en tiempo de compilación si cada servicio que esperamos que maneje el contenedor IoC está registrado correctamente en el código.

¿Cómo se pudo lograr esto? Una cosa es que las Pruebas unitarias están descartadas de posibles respuestas, estoy buscando otra manera, si es que existe.

¿Pensamientos?

EDITAR - Después de algunas respuestas y comentarios, parece que las Pruebas unitarias son de hecho la única forma de lograr esta característica.

Lo que me gustaría saber es que si las pruebas unitarias no fueran posibles, por lo que IoC no podría ser probado en tiempo compilado, esto le impediría usar un contenedor IoC y optaría por crear instancias directas todo tu código? Quiero decir, ¿considerarías demasiado inseguro y arriesgado usar el IoC y el enlace tardío, y ver que sus ventajas están siendo superadas por este "defecto"?

+2

Como @ Chriseyre2000 señaló en su respuesta, Unit Testing es una forma muy efectiva de verificar la configuración de IoC, por lo que puede volver a evaluar su decisión de no tener pruebas. La alternativa a esto es probar manualmente cada página en el sitio web. – GolfWolf

+0

No es una decisión, quiero saber si hay otra manera consistente y confiable de lograr esto aparte de las pruebas unitarias, que es, hasta ahora, la única forma en que soy consciente. –

+0

¿Por qué no podría usar una prueba de unidad de un solo método? – Chriseyre2000

Respuesta

8

Es imposible para el compilador validar el funcionamiento de todo el programa. El hecho de que su programa se compile, no significa que funcione correctamente (incluso sin usar IoC). Para eso, necesitará tanto pruebas automatizadas como pruebas manuales. Esto no significa que no deba tratar de dejar que el compilador haga todo lo que pueda, pero mantenerse alejado de IoC es malo, ya que IoC pretende mantener su aplicación flexible, comprobable y mantenible. Sin IoC no podrá probar su código correctamente, y sin ninguna prueba automatizada es casi imposible escribir cualquier software de tamaño razonable que se pueda mantener.

Sin embargo, tener la flexibilidad que proporciona IoC significa que las dependencias que tiene un fragmento de código en particular no pueden ser validadas por el compilador. Entonces debes hacer esto de otra manera.

Algunos marcos DI le permiten verificar que el contenedor sea correcto. Simple Injector por ejemplo, contiene un método Verify(), que simplemente iterará sobre todos los registros y resolverá una instancia para cada registro. Al invocar este método (o utilizar un enfoque similar) durante el inicio de la aplicación, descubrirá durante la prueba (desarrollador) si algo está mal con la configuración DI y evitará que la aplicación se inicie. Incluso podrías hacer esto en una prueba unitaria.

Sin embargo, es importante que probar la configuración DI no necesite mucho mantenimiento. Si debe agregar una prueba unitaria para cada tipo que se registre para verificar el contenedor, fallará, simplemente porque el registro faltante (y por lo tanto una prueba de unidad faltante) será la razón para fallar en primer lugar.

Esto le brinda soporte "casi" en tiempo de compilación. Sin embargo, debe ser consciente del diseño de su aplicación y de la forma en que conecta las cosas. Estos son algunos consejos:

  1. Manténgase alejado de la inyección de propiedad implícita, donde el contenedor puede omitir inyectar la propiedad si no puede encontrar una dependencia registrada. Esto no permitirá que su aplicación falle rápidamente y dará como resultado NullReferenceException s más adelante. La inyección de propiedad explícita, donde fuerza al contenedor a inyectar una propiedad está bien, sin embargo, use la inyección de constructor siempre que sea posible.
  2. Registre todos los objetos raíz explícitamente si es posible. Por ejemplo, registre todas las instancias de ASP.NET MVC Controller explícitamente en el contenedor. De esta forma, el contenedor puede verificar el gráfico de dependencia completo a partir de los objetos raíz. Debe registrar todos los objetos raíz de forma automatizada, por ejemplo, utilizando la función de reflexión para buscar todos los tipos de raíz. El MVC3 Integration NuGet Package del inyector simple, por ejemplo, contiene un método de extensión RegisterMvcControllers que lo hará por usted. Los paquetes de integración de otros contenedores contienen características similares.
  3. Si registrar objetos raíz no es posible o factible, pruebe la creación de cada objeto raíz manualmente durante el inicio. Con las clases ASP.NET Web Form Page, por ejemplo, probablemente llame al contenedor desde su constructor (ya que las clases Page deben tener un constructor predeterminado). La clave aquí otra vez es encontrarlos a todos en una vez usando la reflexión. Al encontrar todas las clases de página usando reflexión e instanciarlas, descubrirá (durante el inicio de la aplicación o el tiempo de prueba) si hay un problema con su configuración DI o no.
  4. Deje que todos los servicios que su contenedor IoC administra para usted tengan un único constructor público. Varios constructores generan ambigüedad y pueden romper su aplicación de maneras impredecibles. Tener multiple constructors is an anti-pattern.
  5. Existen situaciones en las que aún no se pueden crear algunas dependencias durante el inicio de la aplicación. Para asegurarse de que la aplicación se puede iniciar normalmente y el resto de la configuración DI aún se puede validar, abstraiga esas dependencias detrás de una fábrica proxy o abstracta.
+0

Su respuesta es muy apreciada, pero creo que se equivocó con lo que trato de entender. No necesito verificar si mis servicios registrados se resuelven correctamente. Lo que necesito verificar es que cada servicio solicitado por el código tiene un registro en la configuración. Y quiero entender si el IoC puede considerarse una práctica mala o peligrosa si esto no se puede hacer. –

+1

No debe solicitar sus servicios de su código. En su lugar, inyecte todos los servicios a través de la inyección del constructor y resuelva solo los objetos raíz. De esta forma, se construye un gráfico de objetos completo de una sola vez, y de esta manera usted sabe que todos los servicios se registran correctamente cuando sus objetos raíz se resuelven correctamente. – Steven

+0

Sí, quise decir los objetos raíz por eso. –

2

No puede probar todo su código con un compilador en tiempo de compilación. Suena absurdo Debe escribir diferentes tipos de pruebas de todos modos. Y la configuración del contenedor es un buen candidato para Pruebas de integración. Puede configurar sus pruebas para que se ejecuten automáticamente como un evento posterior a la construcción, pero esto puede aumentar el tiempo de construcción. En cualquier caso, es una mala razón para rechazar el uso de contenedores de IoC.

+0

Estoy totalmente de acuerdo con usted. Eso es lo que suelo hacer de todos modos, solo quería saber si había una alternativa viable. –

+0

"No se puede probar todo el código con un compilador en tiempo de compilación. Parece absurdo". ¿Por qué? Un compilador es un programa que responde a las instrucciones. No hay ninguna razón inherente que un idioma no podría proporcionar no podría admitir un meta-programa de compilación para la prueba. –

+1

El compilador solo contiene análisis de código estático. Pero no lo ejecuta. El registro y la resolución de componentes ocurre en tiempo de ejecución. –

3

Lo que me gustaría saber es, si eran pruebas unitarias - por cualquier razón - no es posible, y por lo tanto COI no podría ponerse a prueba en el momento compilado, ¿esto evitar que utilice un contenedor IoC y optando por instanciación directa en todo su código?

Presenta aquí false choice: utilice un contenedor o bien "instanciación directa en todo su código". Pero aún puedes practicar la inyección de dependencia sin ningún contenedor.En vez de hacer esto:

public void Main(string[] args) 
{ 
    var container = new Container(); 
    // ... register various types here... 

    // only Resolve call in entire application 
    var program = container.Resolve<Program>(); 

    program.Run(); 
} 

que acaba de hacer esto:

public void Main(string[] args) 
{ 
    var c = new C(); 
    var b = new B(c); 
    var a = new A(b); 
    var program = new Program(a); 
    program.Run(); 
} 

fijas que se pueden obtener todas las ventajas de la inyección de dependencias de esta manera: los componentes no crean entre sí y pueden seguir siendo altamente desacoplado La construcción de los componentes sigue siendo responsabilidad de la aplicación composition root, aunque no se utiliza ningún contenedor. También obtiene la verificación de tiempo de compilación que busca.

Dos observaciones más:

  1. más te han etiquetado su pregunta dependency-injection, así que estoy asumiendo que usted está usando la inyección de dependencia de hecho en comparación con el patrón Service Locator. En otras palabras, asumo que no está exponiendo e invocando el contenedor a través de su código, que no es necesario y not recommended. Mi respuesta ya no funciona si lo hace. Por favor cuente como una huelga contra el Localizador de Servicios, no en contra de mi respuesta. Constructor injection es una mejor opción.

  2. los programadores generalmente elegirán usar un contenedor en lugar de este enfoque manual, porque en una aplicación grande puede ser no trivial para mantener el orden de las instancias correctas; puede ser necesario realizar muchos reordenamientos simplemente porque introduce un única nueva dependencia en alguna parte. Los contenedores también ofrecen características adicionales que hacen la vida más fácil.

+0

Tienes razón, no estoy haciendo el Localizador de Servicios, sino DI, ya que es más escalable y mantenible. Creo que Service Locator es realmente un antipatrón.Tiendo a tener instancia de contenedor único con un método genérico Resolver , que internamente utilizará mi contenedor IoC de elección. Le pido que resuelva un objeto T, y él lo instanciará, resolviendo sus dependencias a través de DI. Mi pregunta consistía en entender si la afirmación "IoC es malo porque el código se compila pero podría romperse" contiene agua y si había una alternativa a la prueba unitaria (porque las pruebas se consideran complejas). –

+0

@MatteoMosca: ¿no respondí esa pregunta aquí? He demostrado que puede tener IoC y compilar seguridad de tiempo, sin pruebas unitarias. –

+0

Sí y no. No estoy dispuesto a ceder un contenedor o el marco de trabajo de IoC a favor de esto. Quería saber si mi "necesidad" podría satisfacerse en las condiciones especificadas, y después de todas las respuestas y comentarios, está bastante claro que la respuesta es no, que es lo que pensé desde el principio. Gracias por la ayuda. –

Cuestiones relacionadas