2011-02-01 26 views
9

Me pregunto cómo evitar esto. Estoy usando nhibernate y fluido.Pruebas unitarias y nhibernate?

que tienen una clase de dominio como éste

public class User 
{ 
    public virtual int UserId {get; private set;} 
} 

esto parece ser la convención cuando se hace nhibernate ya que impide que las personas y el establecimiento de Identificación ya que es generada automáticamente.

Ahora el problema viene cuando estoy probando la unidad.

Tengo todo mi código nhibernate en un repositorio que me burlo, así que solo estoy probando mi capa de servicio. El problema viene cuando esto sucede.

User user = repo.GetUser(email); 

esto debería devolver un objeto de usuario.

así que quiero utilizar moq hacer esto

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */) 

ahora aquí está el problema

Necesito hacer ese objeto de usuario y ponerlo en la parte de retorno.

Así que haría algo así

User user = new User() 
{ 
    UserId = 10, 
} 

Pero esto es donde radica el problema que tengo que poner la identificación porque en realidad lo uso más tarde para hacer algo de LINQ en algunas colecciones (en la capa de servicio como no está llegando a mi base de datos así que no debería estar en mi repositorio), así que tengo que configurarlo, pero no puedo configurarlo porque es un conjunto privado.

¿Qué debo hacer? ¿Debería eliminar el privado o hay alguna otra manera?

Respuesta

14

Usted puede tener la falsa Repository objeto devolver una falsa User objeto:

var stubUser = new Mock<User>(); 
stubUser.Setup(s => s.UserId).Returns(10); 

var stubRepo = new Mock<IUserRepository>(); 
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser); 

Hay un par de cosas para observar aquí:

  1. Moq sólo puede falsos miembros de clases concretas si están marcados como virtuales. Esto puede no ser aplicable en algunos escenarios, en cuyo caso la única forma de simular un objeto a través de Moq es hacer que implemente una interfaz.
    En este caso, sin embargo, la solución funciona bien porque NHibernate already imposes the same requirement en las propiedades de la clase User para realizar la carga diferida.
  2. Tener objetos falsos que devuelvan otras falsificaciones a veces puede dar lugar a pruebas unitarias sobre especificadas. En estas situaciones, la construcción de modelos de objetos ricos formados por trozos y burlas crece hasta el punto en que resulta difícil determinar exactamente qué se está probando, lo que hace que la prueba sea ilegible y difícil de mantener. Es una práctica de pruebas de unidad perfectamente precisa, para ser clara, pero debe usarse de forma consciente.

recursos relacionados:

+0

Gracias esto funciona igual perfectamente. Puede que tenga síntomas del número 2. Simplemente tiene que simular tantas cosas. Estoy tratando de mantenerlo también solo lo que necesito probar. Por ejemplo, no me burlo de un nombre de usuario para el usuario, ya que no se utiliza en el método. Me gustaría probar la moc de autofixture pero no entiendo cómo usarla realmente y parece que no hay tutoriales reales sobre ella, así que no puedo determinar si esto me ayudaría o no. – chobo2

2

respuesta de Enrico en el clavo para las pruebas unitarias. Ofrezco otra solución porque este problema surge en otras circunstancias también, donde es posible que no desee usar Moq. Regularmente uso esta técnica en el código de producción, donde el patrón de uso común es para que un miembro de la clase sea de solo lectura, pero algunas otras clases necesitan modificarlo. Un ejemplo podría ser un campo de estado, que normalmente es de solo lectura y solo debe establecerlo una máquina de estados o una clase de lógica de negocios.

Básicamente, proporciona acceso al miembro privado a través de una clase anidada estática que contiene un método para establecer la propiedad. Un ejemplo vale más que mil palabras:

public class User { 
    public int Id { get; private set; } 

    public static class Reveal { 
     public static void SetId(User user, int id) { 
      user.Id = id; 
     } 
    } 
} 

que lo utilice como esto:

User user = new User(); 
User.Reveal.SetId(user, 43); 

Por supuesto, esto entonces permite a cualquier persona para establecer el valor de la propiedad casi tan fácilmente como si se hubiera proporcionado un público setter. Sin embargo, hay algunas ventajas con esta técnica:

  • sin Intellisense solicita la un método SETID()
  • programadores deben utilizar explícitamente la sintaxis extraña para establecer la propiedad con la clase Reveal colocador propiedad o, por lo tanto lo que les empuja que que probablemente no debería estar haciendo esto
  • se puede realizar fácilmente análisis estático en los usos de la clase Reveal para ver qué código está pasando por alto los patrones de acceso estándar

Si sólo está buscando t o modificar una propiedad privada para fines de prueba de la unidad, y usted puede Moq el objeto, entonces aún recomendaría la sugerencia de Enrico; pero puede encontrar esta técnica útil de vez en cuando.

+0

+1. Muchas opciones similares: InternalsVisibleToAttribute, protected and subclass, crack with reflection, ponga SetId en User con ObsoleteAttribute con el texto de advertencia apropiado, pase Id a constructor sobrecargado (mi preferencia cuando sea posible), etc. Me gusta porque fomenta las pruebas con el POCOs/DTOs. Por mucho que ame a Moq, @ Enrico tiene razón sobre los peligros de la sobre-especificación. – TrueWill

0

En lugar de tratar de burlarse de su repositorio, le sugiero que intente utilizar una base de datos SQLite en la memoria para realizar pruebas. Le dará la velocidad que está buscando y facilitará las cosas también. Si desea ver una muestra que funcione, puede echar un vistazo a uno de mis proyectos de GitHub: https://github.com/dlidstrom/GridBook.

1

Otra alternativa si prefiere no burlarse de las clases de entidad es establecer la identificación privada/protegida mediante reflexión.

Sí, sé que esto generalmente no se considera muy favorablemente, y a menudo se cita como un signo de diseño deficiente en alguna parte. Pero en este caso, tener una identificación protegida en sus entidades NHibernate es el paradigma estándar, por lo que parece una solución bastante razonable.

Podemos intentar implementarlo muy bien al menos. En mi caso, el 95% de mis entidades utilizan un único Guid como identificador único, y solo unas pocas usan un entero.Por lo tanto nuestras clases de entidad generalmente implementan una muy simple HasID interfaz:

public interface IHasID<T> 
{ 
    T ID { get; } 
} 

En una clase de entidad real, podríamos aplicar de esta manera:

public class User : IHasID<Guid> 
{ 
    Guid ID { get; protected set; } 
} 

Este ID se asigna a NHibernate como clave primaria en la manera usual.

Para el ajuste de este en nuestras pruebas de unidad, podemos utilizar esta interfaz para proporcionar un práctico método de extensión:

public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id }); 
    return o; 
} 

No tiene que tener la interfaz Hasid para hacer esto, pero significa podemos omitir un poco de código adicional, por ejemplo, no necesitamos verificar si la ID es realmente compatible o no.

El método de extensión también devuelve el objeto original, por lo que en uso por lo general sólo se encadeno en el extremo del constructor:

var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7")); 

O en realidad, ya que los GUID no me importa lo que la realidad ID es decir, tengo otro método de extensión:

public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() }); 
    return o; 
} 

Y en uso:

var testUser = new User("Test User").WithNewGuid();