unit-testing - visual - unit test webapi
MVC 3: ¿Cómo aprender a probar con NUnit, Ninject y Moq? (3)
Versión corta de mis preguntas:
- ¿Alguien puede indicarme algunas fuentes buenas y detalladas desde las cuales puedo aprender a implementar pruebas en mi aplicación MVC 3, usando NUnit, Ninject 2 y Moq?
- ¿Alguien aquí puede ayudarme a aclarar cómo funcionan juntos el desacoplamiento entre Controlador-Repositorio, la burla y la inyección de dependencia?
Versión más larga de mis preguntas:
Lo que intento hacer ...
Actualmente estoy empezando a crear una aplicación MVC 3, que utilizará Entity Framework 4, con un primer enfoque de base de datos. Quiero hacer esto bien, así que estoy tratando de diseñar las clases, capas, etc., para que sean altamente comprobables. Pero, tengo poca o ninguna experiencia con pruebas unitarias o pruebas de integración, que no sean un conocimiento académico de ellos.
Después de mucha investigación, me he decidido a usar
- NUnit como mi marco de prueba
- Ninject 2 como mi marco de inyección de dependencia
- Moq como mi marco de burla.
Sé que el tema de qué marco es el mejor, etc., podría entrar en esto, pero en este punto, realmente no sé lo suficiente sobre nada de eso para formar una opinión sólida. Por lo tanto, decidí ir con estas soluciones gratuitas que parecen ser muy querido y bien mantenido.
Lo que he aprendido hasta ahora ...
Pasé algún tiempo trabajando en algunas de estas cosas, leyendo recursos como:
- Implementación de los patrones de repositorio y unidad de trabajo en una aplicación ASP.NET MVC
- Creación de aplicaciones comprobables de ASP.NET MVC
- NerdDinner Paso 12: Prueba unitaria
- Uso de patrones de repositorio y unidad de trabajo con Entity Framework 4.0
Con estos recursos, pude entrenar la necesidad de un patrón de Repositorio, completo con interfaces de repositorio, para desacoplar mis controladores y mi lógica de acceso a datos. Ya he escrito algo de eso en mi solicitud, pero admito que no tengo claro el mecanismo de todo el asunto, y si estoy haciendo este desacoplamiento en apoyo de la burla o la inyección de dependencia, o ambas cosas. Como tal, ciertamente no me importaría escuchar de ustedes sobre esto también. Cualquier claridad que pueda obtener sobre esto me ayudará en este punto.
Donde las cosas se pusieron turbias para mí ...
Pensé que estaba entendiendo esto bastante bien hasta que comencé a tratar de entender bien Ninject, como se describe en Building Testable ASP.NET MVC Applications , citado anteriormente. Específicamente, me perdí por completo en torno al punto en que el autor comienza a describir la implementación de una capa de Servicio, aproximadamente a la mitad del documento.
De todos modos, ahora estoy buscando más recursos para estudiar, con el fin de tratar de obtener varias perspectivas sobre este tema hasta que empiece a tener sentido para mí.
Resumiendo todo esto, resumiéndolo en preguntas específicas, me pregunto lo siguiente:
- ¿Alguien puede indicarme algunas fuentes buenas y detalladas desde las cuales puedo aprender a implementar pruebas en mi aplicación MVC 3, usando NUnit, Ninject 2 y Moq?
- ¿Alguien aquí puede ayudarme a aclarar cómo funcionan juntos el desacoplamiento entre Controlador-Repositorio, la burla y la inyección de dependencia?
EDITAR:
Acabo de descubrir la wiki oficial de Ninject en Github, así que voy a empezar a trabajar en eso para ver si comienza a aclarar las cosas para mí. Pero, todavía estoy muy interesado en los pensamientos de la comunidad SO sobre todo esto :)
Aquí está la aplicación que estoy creando. Es de código abierto y está disponible en github, y utiliza todas las funciones necesarias: MVC3, NUnit, Moq, Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src
El desacoplamiento Contoller-Repository es simple. Todas las operaciones de datos se mueven hacia el repositorio. Repository es una implementación de algún tipo de IRepository. El controlador nunca crea repositorios dentro de sí mismo (con el
new
operador) sino que los recibe por argumento o propiedad del constructor.
.
public class HomeController {
public HomeController (IUserRepository users) {
}
}
Esta técnica se llama "Inversión de control". Para admitir la inversión de control, debe proporcionar un marco de "inyección de dependencia". Ninject es bueno. Dentro de Ninject se asocia una interfaz particular con una clase de implementación:
Bind<IUserRepository>().To<UserRepository>();
También sustituye la fábrica de controlador predeterminada por la personalizada. Dentro del personalizado, usted delega la llamada al kernel Ninject:
public class TrackyControllerFactory : DefaultControllerFactory
{
private IKernel _kernel = new StandardKernel(new TrackyServices());
protected override IController GetControllerInstance(
System.Web.Routing.RequestContext requestContext,
Type controllerType)
{
if (controllerType == null)
{
return null;
}
return _kernel.Get(controllerType) as IController;
}
}
Cuando la infraestructura de MVC está a punto de crear un nuevo controlador, la llamada se delega al método de fábrica GetControllerInstance de fábrica del controlador personalizado, que lo delega en Ninject. Ninject ve que para crear ese controlador el constructor tiene un argumento de tipo IUserRepository
. Al usar el enlace declarado, ve que "Necesito crear un UserRepository para satisfacer la necesidad de IUserRepository". Crea la instancia y la pasa al constructor.
El constructor nunca sabe qué instancia exacta se pasará dentro. Todo depende del enlace que proporciones para eso.
Ejemplos de código:
- https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Infrastructure/TrackyServices.cs https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Infrastructure/TrackyControllerFactory.cs https://github.com/alexanderbeletsky/trackyt.net/blob/master/src/Web/Controllers/LoginController.cs
Échale un vistazo: video de DDD Melbourne - Nuevo flujo de trabajo de desarrollo
Todo el proceso de desarrollo de ASP.NET MVC 3 estuvo muy bien presentado.
Las herramientas de terceros que más me gustan son:
- Usando NuGet para instalar Ninject para habilitar DI en todo el framework MVC3
- Usando NuGet para instalar nSubstite para crear simulaciones para permitir pruebas unitarias
Si está utilizando el paquete Ninject.MVC3 Ninject.MVC3, entonces no será necesario que algunos de los artículos vinculados que causaron confusión no sean necesarios. Ese paquete tiene todo lo que necesita para comenzar a inyectar sus controladores, que es probablemente el mayor punto de dolor.
Al instalar ese paquete, creará un archivo NinjectMVC3.cs en la carpeta App_Start, dentro de esa clase se encuentra un método RegisterServices. Aquí es donde debe crear los enlaces entre sus interfaces y sus implementaciones
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IRepository>().To<MyRepositoryImpl>();
kernel.Bind<IWebData>().To<MyWebDAtaImpl>();
}
Ahora en tu controlador puedes usar inyección de constructor.
public class HomeController : Controller {
private readonly IRepository _Repo;
private readonly IWebData _WebData;
public HomeController(IRepository repo, IWebData webData) {
_Repo = repo;
_WebData = webData;
}
}
Si buscas una cobertura de prueba muy alta, básicamente, cada vez que una pieza lógica de código (por ejemplo, controlador) necesita hablar con otra (por ejemplo, base de datos) debes crear una interfaz e implementación, agregar la definición vinculante a RegisterService y agregar un nuevo argumento de constructor .
Esto se aplica no solo a Controller, sino a cualquier clase, por lo que en el ejemplo anterior si su implementación de repositorio necesitaba una instancia de WebData para algo, agregaría el campo readonly y el constructor a su implementación de repositorio.
Luego, cuando se trata de pruebas, lo que desea hacer es proporcionar una versión burlada de todas las interfaces requeridas, de modo que lo único que esté probando sea el código del método para el que está escribiendo la prueba. Entonces en mi ejemplo, diga que IRepository tiene un
bool TryCreateUser(string username);
Que se llama por un método de controlador
public ActionResult CreateUser(string username) {
if (_Repo.TryCreateUser(username))
return RedirectToAction("CreatedUser");
else
return RedirectToAction("Error");
}
Lo que realmente está tratando de probar aquí es que si la declaración y los tipos de retorno, no quiere tener que crear un repositorio real que devuelva verdadero o falso en función de los valores especiales que le dé. Aquí es donde quieres burlarte.
public void TestCreateUserSucceeds() {
var repo = new Mock<IRepository>();
repo.Setup(d=> d.TryCreateUser(It.IsAny<string>())).Returns(true);
var controller = new HomeController(repo);
var result = controller.CreateUser("test");
Assert.IsNotNull(result);
Assert.IsOfType<RedirectToActionResult>(result)
Assert.AreEqual("CreatedUser", ((RedirectToActionResult)result).RouteData["Action"]);
}
^ Eso no compilará para ti ya que sé xUnit better, y no recuerdo los nombres de las propiedades en RedirectToActionResult desde lo alto de mi cabeza.
Entonces, para resumir, si quiere que una parte del código le hable a otra, golpee una interfaz en el medio. Esto le permite simular el segundo fragmento de código para que cuando pruebe el primero pueda controlar el resultado y asegúrese de estar probando solo el código en cuestión.
Creo que fue este punto lo que realmente hizo que el centavo cayera para mí con todo esto, no necesariamente porque el código lo exija, sino porque las pruebas lo exigen.
Un último consejo específico para MVC, cada vez que necesites acceder a objetos web básicos, HttpContext, HttpRequest, etc., envuelve todo esto detrás de una interfaz (como IWebData en mi ejemplo) porque puedes simular esto usando el * Las clases base, se vuelven dolorosas muy rápidamente ya que tienen muchas dependencias internas que también necesita para burlarse.
También con Moq, configure MockBehaviour en estricto al crear simulaciones y le dirá si se está llamando a algo que no haya proporcionado.