c# - injection - ¿Cuál es un buen enfoque para probar los enlaces de Ninject?
ninject tutorial español (2)
Usamos ninject en todos nuestros proyectos, y como sabrá, a veces resulta difícil probar si el kernel podría resolver todos los tipos en el momento de la ejecución, porque a veces el control se pierde cuando la magnitud de los enlaces y las vinculaciones automáticas (a través de las extensiones de ninject ) es alto.
Entonces, lo que estoy preguntando aquí es ¿cómo puedo saber que mi kernel, después de cargar todos los módulos y enlaces, podrá resolver todos los tipos? ¿Haces algún tipo de Test de Unidad? ¿O simplemente hace pruebas de aceptación de la aplicación, en el momento de la ejecución? Cualquier sugerencia será genial :)
Entonces, lo que estoy preguntando aquí es ¿cómo puedo saber que mi kernel, después de cargar todos los módulos y enlaces, podrá resolver todos los tipos? ¿Haces algún tipo de Test de Unidad?
Pruebo esto haciendo un bucle sobre cada uno de los enlaces en mi (s) módulo (s) y comprobando que el Kernel pueda devolver algo para ellos:
[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}");
}
}
Escriba una prueba de integración que pruebe la configuración del contenedor haciendo un bucle sobre todos los tipos de raíz en la aplicación y solicitándolos al contenedor / kernel. Al solicitarlos del contenedor, está seguro de que el contenedor puede acumular el gráfico de objetos completo para usted.
Un tipo de raíz es un tipo que se solicita directamente desde el contenedor. La mayoría de los tipos no serán tipos de raíz, sino parte del gráfico de objetos (ya que rara vez debería volver a llamar al contenedor desde la aplicación). Cuando pruebe la creación de un tipo de raíz, inmediatamente probará la creación de todas las dependencias de ese tipo de raíz, a menos que haya proxies, fábricas u otros mecanismos que puedan retrasar el proceso de construcción. Sin embargo, los mecanismos que retrasan el proceso de construcción apuntan a otros objetos raíz. Debes identificarlos y probar su creación.
Evite tener una prueba enorme con una llamada al contenedor para cada tipo de raíz. En su lugar, cargue (si es posible) todos los tipos de raíz utilizando la reflexión y repita sobre ellos. Al utilizar algún tipo de método de convención sobre configuración, se ahorra tener que 1. cambiar la prueba para cada nuevo tipo de raíz, y 2. evitar una prueba incompleta cuando se olvidó de agregar una prueba para un nuevo tipo de raíz.
Aquí hay un ejemplo para ASP.NET MVC donde sus tipos de raíz son controladores:
[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);
}
}
ACTUALIZAR
Sebastian Weber hizo un comentario interesante al que me gustaría responder.
¿Qué pasa con la creación tardía de objetos usando fábricas respaldadas por contenedores (Func) o generadas por contenedores (como las instalaciones de fábrica tipificadas de Castle)? No los atraparás con ese tipo de prueba. Eso te daría un falso sentimiento de seguridad.
Mi consejo es sobre la verificación de todos los tipos de raíz . Los servicios que se crean de manera diferida son, de hecho, tipos de raíz y, por lo tanto, deben probarse explícitamente. Por supuesto, esto le obliga a monitorear de cerca los cambios en su configuración, y agregar una prueba cuando detecta que se agrega un nuevo tipo de raíz que no se puede probar con las pruebas de convención sobre configuración que ya tiene implementadas. Esto no es malo, ya que nadie dice que usar DI y un contenedor DI significa que podemos descuidarnos de repente. Se necesita disciplina para crear un buen software, ya sea que uses DI o no.
Por supuesto, este enfoque será bastante incómodo cuando tengas muchos registros que retrasen la creación. En ese caso, es probable que haya algún problema con el diseño de su aplicación, ya que el uso de la creación retrasada debe ser la excepción, no la norma. Otra cosa que podría causarle problemas es cuando su contenedor le permite resolver registros Func<T>
registrados, asignándolos a un () => container.GetInstance<T>()
delegado. Esto suena bien, pero te obliga a mirar más allá del registro del contenedor para buscar tipos de raíz, y hace que sea mucho más fácil perder uno. Dado que el uso de la creación diferida debería ser la excepción, es mejor que se encuentre con el registro explícito.
También tenga en cuenta que incluso si no puede probar el 100% de su configuración, esto no significa que hacer que la configuración sea inútil. No podemos probar automáticamente el 100% de nuestro software y debemos cuidar especialmente esa parte de nuestro software / configuración que no se puede probar automáticamente. Por ejemplo, puede agregar partes no verificables a un script de prueba manual y probarlas a mano. Por supuesto, cuanto más tenga que probar a mano, más puede (y lo hará) ir mal, por lo que debe intentar maximizar la capacidad de prueba de su configuración (como debería hacer con todo su software). Por supuesto, obtendrá una falsa sensación de seguridad cuando no sepa cuáles son sus pruebas, pero nuevamente, esto se aplica a todo en nuestra profesión.