Este es un tema que me interesa mucho. Hay muchos puristas que dicen que no debe probar tecnologías como EF y NHibernate. Tienen razón, ya se han probado de manera muy estricta y, como una respuesta anterior declaró, a menudo no tiene sentido gastar grandes cantidades de tiempo probando lo que no posee.
Sin embargo, usted posee la base de datos debajo.Aquí es donde este enfoque, en mi opinión, se rompe, no es necesario probar que EF / NH están haciendo su trabajo correctamente. Debe comprobar que sus asignaciones / implementaciones funcionan con su base de datos. En mi opinión, esta es una de las partes más importantes de un sistema que puede probar.
Hablando estrictamente, sin embargo, nos estamos moviendo fuera del dominio de las pruebas unitarias y en las pruebas de integración, pero los principios siguen siendo los mismos.
Lo primero que debe hacer es poder burlarse de su DAL para que su BLL pueda probarse independientemente de EF y SQL. Estas son sus pruebas unitarias. A continuación, debe diseñar sus pruebas de integración para probar su DAL, en mi opinión, estas son igual de importantes.
Hay un par de cosas a considerar:
- Su base de datos debe estar en un estado conocido con cada prueba. La mayoría de los sistemas usan una copia de seguridad o crean scripts para esto.
- Cada prueba debe ser repetible
- Cada prueba debe ser atómica
Hay dos enfoques principales para configurar su base de datos, el primero es ejecutar un script de creación de DB de UnitTest. Esto asegura que su base de datos de prueba de unidad siempre estará en el mismo estado al comienzo de cada prueba (puede restablecer esto o ejecutar cada prueba en una transacción para garantizar esto).
Su otra opción es lo que hago, ejecutar configuraciones específicas para cada prueba individual. Creo que este es el mejor enfoque por dos razones principales:
- Su base de datos es más simple, no necesita un esquema completo para cada prueba
- Cada prueba es más segura, si cambia un valor en su script de creación, no invalida docenas de otras pruebas.
Lamentablemente, su compromiso aquí es la velocidad. Lleva tiempo ejecutar todas estas pruebas, ejecutar todos estos scripts de configuración / desmontaje.
Un último punto, puede ser muy difícil escribir una cantidad tan grande de SQL para probar su ORM. Aquí es donde adopto un enfoque muy desagradable (los puristas aquí estarán en desacuerdo conmigo). ¡Utilizo mi ORM para crear mi prueba! En lugar de tener un script separado para cada prueba DAL en mi sistema, tengo una fase de configuración de prueba que crea los objetos, los adjunta al contexto y los guarda. Luego ejecuto mi prueba.
Esto está lejos de ser la solución ideal, sin embargo, en la práctica, encuentro que es MUCHO más fácil de administrar (especialmente cuando tiene varios miles de pruebas), de lo contrario, está creando un gran número de scripts. Practicidad sobre la pureza.
Sin duda volveré a mirar esta respuesta en unos pocos años (meses / días) y estaré en desacuerdo conmigo mismo ya que mis enfoques han cambiado; sin embargo, este es mi enfoque actual.
Para intentar resumir todo lo que he dicho anteriormente, esta es mi típica prueba de integración de base de datos:
[Test]
public void LoadUser()
{
this.RunTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
return user.UserID;
}, id => // the ID of the entity we need to load
{
var user = LoadMyUser(id); // load the entity
Assert.AreEqual("Mr", user.Title); // test your properties
Assert.AreEqual("Joe", user.Firstname);
Assert.AreEqual("Bloggs", user.Lastname);
}
}
La clave para notar aquí es que las sesiones de los dos bucles son completamente independientes. En su implementación de RunTest, debe asegurarse de que el contexto se confirma y destruye y sus datos solo pueden provenir de su base de datos para la segunda parte.
Editar 13/10/2014
Dije que probablemente revisaría este modelo en los próximos meses. Si bien defiendo en gran medida el enfoque que defendí anteriormente, actualicé ligeramente mi mecanismo de prueba. Ahora tiendo a crear las entidades en TestSetup y TestTearDown.
[SetUp]
public void Setup()
{
this.SetupTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
});
}
[TearDown]
public void TearDown()
{
this.TearDownDatabase();
}
Luego pruebe cada propiedad individualmente
[Test]
public void TestTitle()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Mr", user.Title);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Bloggs", user.Lastname);
}
Hay varias razones para este enfoque:
- No hay llamadas adicionales a la base de datos (una configuración, un desmontaje)
- Las pruebas son mucho más granulares, cada prueba verifica una propiedad
- La lógica de configuración / TearDown se elimina de los propios métodos de prueba
Creo que esto hace que la clase de prueba sea más simple y las pruebas más granulares (las afirmaciones simples son buenas )
Editar 3/5/2015
Otra revisión de este enfoque. Si bien las configuraciones de nivel de clase son muy útiles para pruebas como las propiedades de carga, son menos útiles cuando se requieren las diferentes configuraciones. En este caso, configurar una nueva clase para cada caso es excesivo.
Para ayudar con esto, ahora tiendo a tener dos clases base SetupPerTest
y SingleSetup
. Estas dos clases exponen el marco según sea necesario.
En el SingleSetup
tenemos un mecanismo muy similar al descrito en mi primera edición. Un ejemplo sería
public TestProperties : SingleSetup
{
public int UserID {get;set;}
public override DoSetup(ISession session)
{
var user = new User("Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Bloggs", user.Lastname);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
}
Sin embargo, las referencias que aseguran que solo se cargan las entidades correctas pueden usar un enfoque SetupPerTest
public TestProperties : SetupPerTest
{
[Test]
public void EnsureCorrectReferenceIsLoaded()
{
int friendID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriend();
session.Save(user);
friendID = user.Friends.Single().FriendID;
} () =>
{
var user = GetUser();
Assert.AreEqual(friendID, user.Friends.Single().FriendID);
});
}
[Test]
public void EnsureOnlyCorrectFriendsAreLoaded()
{
int userID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriends(2);
var user2 = CreateUserWithFriends(5);
session.Save(user);
session.Save(user2);
userID = user.UserID;
} () =>
{
var user = GetUser(userID);
Assert.AreEqual(2, user.Friends.Count());
});
}
}
En resumen, ambos enfoques funcionan según lo que intente probar.