6

Empiezo a adentrarme en Pruebas Unitarias, Inyección de Dependencia y todo ese jazz mientras construyo mi último proyecto ASP.NET MVC.¿Cómo puede probar la unidad sus controladores sin un contenedor IoC?

Ahora llego al punto en el que me gustaría poner a prueba mis controladores y estoy teniendo dificultades para descubrir cómo hacerlo de manera apropiada sin un contenedor IoC.

Tomemos por ejemplo un controlador simple:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // ... Continue with various controller actions 
} 

Esta clase no es muy comprobable unidad debido a su instanciación directa de SqlQuestionsRepository. Por lo tanto, vamos a ir por la ruta de Inyección de Dependencia y hacer:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository; 

    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

Esto parece mejor. Ahora puedo escribir fácilmente pruebas unitarias con un simulador IQuestionsRepository. Sin embargo, ¿qué va a crear una instancia del controlador ahora? En algún lugar más arriba de la cadena de llamadas SqlQuestionRepository tendrá que crearse una instancia. Parece que simplemente cambié el problema a otra parte, no me deshice de él.

Ahora, sé que este es un buen ejemplo de cómo un contenedor de IoC puede ayudarlo al conectar las dependencias de los Controladores mientras que, al mismo tiempo, mantengo mi controlador fácilmente comprobable por unidad.

Mi pregunta es, ¿cómo se supone que uno haga pruebas unitarias en cosas de esta naturaleza sin un contenedor IoC?

Nota: No me opongo a los contenedores de IoC, y es probable que pase por ese camino pronto. Sin embargo, tengo curiosidad por saber cuál es la alternativa para las personas que no los usan.

Respuesta

2

¿No es posible mantener la instanciación directa del campo y también proporcionar el colocador? En este caso, solo estarías llamando al colocador durante la prueba unitaria. Algo como esto:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // Really only called during unit testing... 
    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

no estoy muy familiarizado con .NET, sino como una nota al margen en Java esto es una forma común de refactorizar el código existente para mejorar la capacidad de prueba. I.E., si tiene clases que ya están en uso y necesita modificarlas para mejorar la cobertura del código sin romper la funcionalidad existente.

Nuestro equipo ya lo ha hecho antes, y normalmente establecemos la visibilidad del colocador para el paquete privado y mantenemos el paquete de la clase de prueba igual para que pueda llamar al colocador.

+0

@Peter, muy buena sugerencia. ¿Ha tenido alguna experiencia con el uso de contenedores IoC en sus proyectos o aún no ha encontrado una necesidad? – mmcdole

+0

No tengo mucha experiencia con .NET, la mayor parte de mi trabajo es con Java utilizando Spring para IoC. Ha sido muy útil para mejorar la modularidad. – Peter

+0

@Peter: este es más o menos el patrón que uso. Publiqué una respuesta que hace lo mismo, pero usa algunas características del lenguaje C# de las que usted, como persona de Java, podría no estar al tanto. –

2

Puede tener un constructor predeterminado con su controlador que tendrá algún tipo de comportamiento predeterminado.

Algo así como ...

public QuestionsController() 
    : this(new QuestionsRepository()) 
{ 
} 

esa manera de forma predeterminada al controlador de la fábrica es la creación de una nueva instancia del controlador que utilizará el comportamiento del constructor por defecto. Luego, en las pruebas de su unidad, podría usar un marco de burla para pasar una simulación al otro constructor.

1

Una opción es utilizar falsificaciones.

public class FakeQuestionsRepository : IQuestionsRepository { 
    public FakeQuestionsRepository() { } //simple constructor 
    //implement the interface, without going to the database 
} 

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repository = new FakeQuestionsRepository(); 
     var controller = new QuestionsController(repository); 
     //assert some things on the controller 
    } 
} 

Otra opción es utilizar simulaciones y un marco de burla, que puede generar automáticamente estos simulacros sobre la marcha.

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repositoryMock = new Moq.Mock<IQuestionsRepository>(); 
     repositoryMock 
      .SetupGet(o => o.FirstQuestion) 
      .Returns(new Question { X = 10 }); 
     //repositoryMock.Object is of type IQuestionsRepository: 
     var controller = new QuestionsController(repositoryMock.Object); 
     //assert some things on the controller 
    } 
} 

En cuanto a dónde se construyen todos los objetos. En una prueba unitaria, solo configura un conjunto mínimo de objetos: un objeto real que está bajo prueba, y algunas dependencias falsas o burladas que requiere el objeto real bajo prueba. Por ejemplo, el objeto real bajo prueba es una instancia de QuestionsController - tiene una dependencia en IQuestionsRepository, por lo que le damos un falso IQuestionsRepository como en el primer ejemplo o un simulacro IQuestionsRepository como en el segundo ejemplo.

En el sistema real, sin embargo, configura todo el contenedor en el nivel superior del software. En una aplicación web, por ejemplo, configura el contenedor, conectando todas las interfaces y las clases de implementación, en GlobalApplication.Application_Start.

0

Estoy expandiendo la respuesta de Peter un poco.

En las aplicaciones con muchos tipos de entidades, no es raro que un controlador requiera referencias a múltiples repositorios, servicios, lo que sea. Me resulta tedioso pasar manualmente todas esas dependencias en mi código de prueba (especialmente dado que una prueba determinada solo puede involucrar a una o dos de ellas). En esos escenarios, prefiero el estilo de inyección setter IOC sobre la inyección de constructores. El patrón lo uso esto:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository Repository 
    { 
     get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); } 
     set { _repo = value; } 
    } 
    private IQuestionsRepository _repo; 

    // Don't need anything fancy in the ctor 
    public QuestionsController() 
    { 
    } 
} 

Reemplazar IoC.GetInstance<> con cualquier sintaxis utiliza su marco COI en particular.

En uso de producción, nada invocará al creador de propiedades, por lo que la primera vez que se llame al eliminador el controlador llamará a su marco IOC, obtendrá una instancia y la almacenará.

En la prueba, sólo tiene que llamar a la incubadora antes de invocar cualquier método de controlador:

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
} 

Los beneficios de este enfoque, en mi humilde opinión:

  1. Aún así se aprovecha de la COI en la producción.
  2. Más fácil de construir manualmente sus controladores durante las pruebas. Solo necesita inicializar las dependencias que su prueba realmente usará.
  3. Posible crear configuraciones de IOC específicas para la prueba, si no desea configurar manualmente las dependencias comunes.
Cuestiones relacionadas