mvc - pruebas unitarias c# visual studio 2017
Pruebas unitarias y comprobaciĆ³n del valor de la variable privada (6)
Diría que la "mejor práctica" es probar algo significativo con el objeto que es diferente ahora que almacenó la entidad en la lista.
En otras palabras, ¿qué comportamiento es diferente sobre la clase ahora que la almacenó y prueba ese comportamiento? El almacenamiento es un detalle de implementación.
Dicho esto, no siempre es posible hacer eso.
Puedes usar el reflection si es necesario.
Estoy escribiendo pruebas unitarias con C #, NUnit y Rhino Mocks. Aquí están las partes relevantes de una clase que estoy probando:
public class ClassToBeTested
{
private IList<object> insertItems = new List<object>();
public bool OnSave(object entity, object id)
{
var auditable = entity as IAuditable;
if (auditable != null) insertItems.Add(entity);
return false;
}
}
Quiero probar los valores en insertItems después de una llamada a OnSave:
[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
Setup();
myClassToBeTested.OnSave(auditableObject, null);
// Check auditableObject has been added to insertItems array
}
¿Cuál es la mejor práctica para esto? He considerado agregar insertItems como una propiedad con un get público o inyectar una lista en ClassToBeTested, pero no estoy seguro de que deba modificar el código para fines de prueba.
He leído muchas publicaciones sobre pruebas de métodos privados y refactorización, pero esta es una clase tan simple que me pregunto cuál es la mejor opción.
El número de pruebas que necesita depende de la complejidad del código: cuántos puntos de decisión hay, aproximadamente. Diferentes algoritmos pueden lograr el mismo resultado con diferente complejidad en su implementación. ¿Cómo se escribe una prueba que es independiente de la implementación y aún así se asegura de que tenga una cobertura adecuada de sus puntos de decisión?
Ahora, si está diseñando pruebas más grandes, por ejemplo, en el nivel de integración, entonces no, no le gustaría escribir en la implementación o probar métodos privados, pero la pregunta se dirigió al alcance de la prueba pequeña, unidad.
La respuesta rápida es que nunca debe tener acceso a miembros no públicos de las pruebas de su unidad. Desafía por completo el objetivo de tener un conjunto de pruebas, ya que lo bloquea en los detalles internos de implementación que tal vez no desee mantener de esa manera.
La respuesta más larga se refiere a qué hacer entonces? En este caso, es importante entender por qué la implementación es tal como está (esta es la razón por la cual el TDD es tan poderoso, porque usamos las pruebas para especificar el comportamiento esperado, pero tengo la sensación de que no está usando TDD).
En su caso, la primera pregunta que se le viene a la mente es: "¿Por qué se agregan los objetos auditables a la lista interna?" o, dicho de otra manera, "¿Cuál es el resultado externo visible esperado de esta implementación?" Dependiendo de la respuesta a esas preguntas, eso es lo que necesita probar.
Si agrega los objetos habilitables de IA a su lista interna porque más tarde desea escribirlos en un registro de auditoría (simplemente una suposición descabellada), invoque el método que escribe el registro y verifique que se escribieron los datos esperados.
Si agrega el objeto habilitable para IA a su lista interna porque desea acumular evidencia contra algún tipo de Restricción posterior, intente probarlo.
Si agregaste el código sin una razón medible, entonces elimínalo de nuevo :)
La parte importante es que es muy beneficioso probar el comportamiento en lugar de la implementación . También es una forma de prueba más robusta y mantenible.
No tenga miedo de modificar su Sistema bajo prueba (SUT) para que sea más comprobable. Siempre que sus adiciones tengan sentido en su dominio y sigan las mejores prácticas orientadas a objetos, no hay problemas, usted simplemente estaría siguiendo el Principio Abierto / Cerrado .
No debería verificar la lista donde se agregó el artículo. Si haces eso, escribirás una prueba unitaria para el método Add en la lista, y no una prueba para tu código. Simplemente verifique el valor de retorno de OnSave; eso es realmente todo lo que quieres probar.
Si realmente está preocupado por el Add, protéjalo de la ecuación.
Editar:
@TonE: después de leer sus comentarios, diría que tal vez quiera cambiar su método OnSave actual para informarle sobre fallas. Puedes elegir lanzar una excepción si el lanzamiento falla, etc. Luego puedes escribir una prueba de unidad que espera y una excepción, y otra que no.
Si la lista es un detalle de implementación interna (y parece serlo), entonces no debe probarla.
Una buena pregunta es, ¿cuál es el comportamiento que se esperaría si el artículo se añadiera a la lista? Esto puede requerir otro método para activarlo.
public void TestMyClass()
{
MyClass c = new MyClass();
MyOtherClass other = new MyOtherClass();
c.Save(other);
var result = c.Retrieve();
Assert.IsTrue(result.Contains(other));
}
En este caso, estoy afirmando que el comportamiento correcto, visible externamente, es que después de guardar el objeto, se incluirá en la colección recuperada.
Si el resultado es que, en el futuro, el objeto transferido debería recibir una llamada en determinadas circunstancias, entonces podría tener algo como esto (por favor perdone pseudo-API):
public void TestMyClass()
{
MyClass c = new MyClass();
IThing other = GetMock();
c.Save(other);
c.DoSomething();
other.AssertWasCalled(o => o.SomeMethod());
}
En ambos casos, está probando el comportamiento externo visible de la clase, no la implementación interna.
Si no me equivoco, lo que realmente quiere probar es que solo agrega elementos a la lista cuando se pueden convertir a IAuditable
. Por lo tanto, puede escribir algunas pruebas con nombres de métodos como:
- NotIAuditableIsNotSaved
- IAuditableInstanceIsSaved
- IAuditableSubclassInstanceIsSaved
... Etcétera.
El problema es que, como observas, dado el código en tu pregunta, solo puedes hacerlo indirectamente, solo marcando el insertItems IList<object>
privado insertItems IList<object>
(por reflexión o agregando una propiedad con el único propósito de probar) o inyectando la lista en la clase:
public class ClassToBeTested
{
private IList _InsertItems = null;
public ClassToBeTested(IList insertItems) {
_InsertItems = insertItems;
}
}
Entonces, es simple de probar:
[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
Setup();
List<object> testList = new List<object>();
myClassToBeTested = new MyClassToBeTested(testList);
// ... create audiableObject here, etc.
myClassToBeTested.OnSave(auditableObject, null);
// Check auditableObject has been added to testList
}
La inyección es la solución más avanzada y discreta a menos que tenga algún motivo para pensar que la lista sería una parte valiosa de su interfaz pública (en cuyo caso agregar una propiedad podría ser superior, y por supuesto, la propiedad también es legítima). Incluso podría conservar un constructor sin argumentos que proporcione una implementación predeterminada (nueva Lista ()).
De hecho, es una buena práctica; Puede parecer un poco sobredimensionado, dado que es una clase simple, pero la capacidad de prueba solo vale la pena. Además, si encuentras otro lugar donde quieras utilizar la clase, eso será la guinda del pastel, ya que no estarás limitado a usar un IList (no es que te cueste mucho hacer el cambio más tarde). )