2009-06-01 12 views
13

he diseñado una aplicación que utiliza el patrón de repositorio, y luego una capa de servicio independiente como este:TDD - ¿Quieres probar mi capa de servicio con un repositorio falso, pero cómo?

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     IRepository<User> userRepository = new UserRepository(); 
     // add user, etc 
    } 
} 

Como se puede ver, estoy crear instancias de mi repositorio en el interior del método de Registro. Ahora que quiero escribir algunas pruebas unitarias, realmente no puedo acceder y reemplazarlo con un repositorio falso, ¿o sí?

Aunque no quiero agregar el repositorio como variable de clase (y configurarlo a través de un constructor) porque creo que eso haría que mi código "oliese mal" (no todos los repositorios son necesarios para todos los métodos, y no lo hago Necesito la capa de llamada para conocer repositorios, etc.

Sugerencias?

+1

Estás en el camino correcto, pero pasar en una dependencia a través del constructor no hace que tu código huele mal. Es un principal de diseño aceptado (Inversión de control). – ebrown

+0

Esta es una muy buena charla sobre Pruebas unitarias con DI: http://www.youtube.com/watch?v=wEhu57pih5w –

Respuesta

16

Debe usar Inyección de dependencia. UserRepository es una dependencia de su clase RegistrationService. Para hacer que sus clases sean adecuadamente comprobables por unidad (es decir, en forma aislada de sus dependencias), debe "invertir" lo que controla su creación de dependencia. Actualmente, usted tiene control directo y los está creando internamente. Sólo invertir ese control, y permitir que algo externo (como un contenedor IoC como el castillo de Windsor) inyectarlos:

public class RegistrationService: IRegistrationService 
{ 
    public RegistrationService(IRepository<User> userRepo) 
    { 
     m_userRepo = userRepo; 
    } 

    private IRepository<User> m_userRepo; 

    public void Register(User user) 
    { 
     // add user, etc with m_userRepo 
    } 
} 

Creo que se me olvidó añadir. Una vez que permite que se inyecten sus dependencias, abre la posibilidad de que sean burlables. Como su RegistrationService ahora toma su repositorio de usuario dependiente como entrada para su constructor, puede crear un repositorio simulado y pasarlo, en lugar de un repositorio real.

+0

Supongo que este es el camino a seguir, entonces ... No quería tener mi IRepository como una variable de clase, pero supongo que para la inyección es el camino a seguir y luego sobrecargar el constructor. ¡Gracias! – Alex

+0

En lugar de inyectar el repositorio en sí, puede inyectar una fábrica y crear el repositorio bajo demanda a partir de eso. – jrista

0

Puede usar un contenedor IOC y luego registrar un repositorio diferente.

9

inyección de dependencias puede venir en muy práctico para este tipo de cosas:

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     this(user, new UserRepository()); 
    } 

    public void Register(User user, IRepository<User> userRepository) 
    { 
     // add user, etc 
    } 

} 

esta manera se obtiene la capacidad de utilizar UserRepository como predeterminado, y pasar en una costumbre (Mock o talón para las pruebas) IRepository para cualquier otra cosa que necesites Es posible que también desee cambiar el alcance del segundo método de registro en caso de que no desee exponerlo a todo.

Una alternativa aún mejor sería usar un contenedor IoC que le compre una sintaxis aún más desacoplada que en el ejemplo anterior. Se podía obtener algo como esto:

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     this(user, IoC.Resolve<IRepository<User>>()); 
    } 

    public void Register(User user, IRepository<User> userRepository) 
    { 
     // add user, etc 
    } 

} 

Hay muchos contenedores IoC por ahí, según el listado de respuestas de otras personas, o usted podría roll your own.

+0

Esto se ve bastante bien, pero tiene el inconveniente de que necesitaría crear un segundo método con el parámetro userRepository para cada método en RegistrationService ... – Alex

+1

@Alex Eso es correcto. Si no quería hacer eso, podría inyectar alternativamente el repositorio en su constructor, pero se estaba alejando de eso en su pregunta. – Joseph

+0

@Alex Sin embargo, también tenga en cuenta que uno de los métodos es solo un contenedor para definir el comportamiento "predeterminado", y no tiene ningún comportamiento real en él. – Joseph

0

Mi sugerencia sería mover el repositorio a un parámetro de clase y usar un contenedor IoC para inyectar el repositorio cuando sea necesario. De esta manera, el código es simple y comprobable.

2

Lo que debe hacer es usar Inversion of Control o Dependency injection.

Sus clases no deben vincularse a una implementación de otra clase, particularmente en capas como esta, sino que debe tomar como interfaz una interfaz que represente un repositorio de usuario como constructor o como propiedad. Luego, cuando se ejecuta, se proporciona la implementación concreta y se usa eso. Puede construir falsificaciones y luego implementar la interfaz del repositorio y suministrarlas en el momento de la prueba.

Hay muchos contenedores IOC, Unity, Ninject, Castle Windsor y StructureMap por nombrar algunos.


sólo quería ser claro, durante sus pruebas de unidad, no sé que te gustaría realmente uso el COI de contenedores. Pruebas de integración, seguro, pero para las pruebas unitarias, simplemente actualice el objeto que está probando y proporcione su falso durante ese proceso. Los contenedores IOC están, al menos, en la OMI, para ayudar en los niveles de prueba de integración o aplicación, y hacen que la configuración sea más fácil.

Las pruebas de su unidad probablemente deberían mantenerse 'puras' y simplemente trabajar con los objetos que está tratando de probar. Al menos eso es lo que funciona para mí.

1

Solo para agregar un poco de claridad (o tal vez no) a lo que todos dijeron, la forma en que un COI funciona básicamente es que le digas que reemplace X con Y. Entonces, lo que harías es aceptar IRepository (como otros han dicho) como un param y luego dentro del IOC le dices que reemplace IRepository con MockedRepository (por ejemplo) para esa clase específica.

Si utiliza un IoC que permite la inicialización de web.config, hace que la transición de dev a prod sea muy fluida. Simplemente modifique los parámetros adecuados dentro del archivo de configuración y estará en camino, sin necesidad de tocar el código. Más tarde, si decide que quiere cambiar a una fuente de datos diferente, simplemente apáguela y estará en camino.

IoC es algo divertido.

Como nota al margen, puede hacerlo sin la inyección de IOC, si todo lo que quiere hacer es probar la unidad. Podrías hacer esto al sobrecargar al constructor para aceptar un IRepository y simplemente pasar tu repositorio simulado de esa manera.

+0

Gracias por la información adicional sobre IOC :) – Alex

1

Estoy trabajando en algo, bueno, casi al pie de la letra en este momento. Estoy usando MOQ para simular una instancia proxy de mi IUserDataRepository.

Desde la perspectiva de la prueba:

//arrange 
var mockrepository = new Mock<IUserDataRepository>(); 
// InsertUser method takes a MembershipUser and a string Role 
mockrepository.Setup(repos => repos.InsertUser(It.IsAny<MembershipUser>(), It.IsAny<string>())).AtMostOnce(); 

//act 
var service = new UserService(mockrepository.Object); 
var createResult = service.CreateUser(new MembershipUser(), "WebUser"); 

//assert 
Assert.AreEqual(MembershipCreateUserResult.Success, createResult); 
mockrepository.VerifyAll(); 

MOQ también se puede utilizar para lanzar excepciones específicas, así por lo que mi UserService se puede probar en esas situaciones.

Luego, como han mencionado otros, IoC manejará las dependencias requeridas. En este caso, crear un repositorio real para la clase UserService.

Cuestiones relacionadas