2009-07-02 23 views
5

así que tengo una clase con un método de la siguiente manera:¿Cómo se prueba la unidad de un detalle de implementación, como el almacenamiento en caché

public class SomeClass 
{ 
    ... 

    private SomeDependency m_dependency; 

    public int DoStuff() 
    { 
     int result = 0; 
     ... 
     int someValue = m_dependency.GrabValue(); 
     ... 
     return result; 
    } 
} 

Y he decidido que en lugar de llamar a m_dependency.GrabValue() cada vez, realmente quiero caché el valor en la memoria (es decir, en esta clase) ya que vamos a obtener el mismo valor cada vez de todos modos (la dependencia se desactiva y toma algunos datos de una tabla que casi nunca cambia).

Sin embargo, he tenido problemas tratando de describir este nuevo comportamiento en una prueba unitaria. He intentado lo siguiente (estoy usando NUnit con RhinoMocks):

[Test] 
public void CacheThatValue() 
{ 
    var depend = MockRepository.GeneraMock<SomeDependency>(); 

    depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1); 

    var sut = new SomeCLass(depend); 
    int result = sut.DoStuff(); 
    result = sut.DoStuff(); 
    depend.VerifyAllExpectations(); 
} 

Sin embargo, esto no funciona; esta prueba pasa incluso sin introducir ningún cambio en la funcionalidad. ¿Qué estoy haciendo mal?

+2

Perdón por hacer la pregunta, pero ¿por qué probar algo que es un detalle de implementación? – Robert

Respuesta

5

Veo el almacenamiento en caché como ortogonal a Do (ing) Stuff. Encontraría una manera de extraer la lógica de almacenamiento en caché fuera del método, ya sea cambiando SomeDependency o envolviéndolo de alguna manera (ahora tengo una idea genial para una clase de caché basada en expresiones lambda - yum).

De esta manera, sus pruebas para DoStuff no necesitan cambiar, solo necesita asegurarse de que funcionen con la nueva envoltura. Luego puede probar la funcionalidad de caché de SomeDependency, o su envoltorio, de forma independiente. Con un código bien estructurado, colocar una capa de almacenamiento en caché debería ser bastante fácil y ni su dependencia ni su implementación deberían saber la diferencia.

Las pruebas unitarias no deben probar la implementación, deben probar el comportamiento. Al mismo tiempo, el sujeto sometido a prueba debe tener un conjunto de comportamientos estrechamente definidos.

Para responder a su pregunta, está utilizando un simulacro dinámico y el comportamiento predeterminado es permitir cualquier llamada que no esté configurada. Las llamadas adicionales simplemente devuelven "0". Es necesario establecer una expectativa de que no hay más llamadas se realizan en la dependencia:

depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1); 
depend.Expect(d => d.GrabValue()).Repeat.Never(); 

Puede que tenga que entrar en el modo de grabación/reproducción de conseguir que funcione correctamente.

+0

Buen punto: "Las pruebas unitarias no deberían probar la implementación, deberían probar el comportamiento. Al mismo tiempo, el sujeto sometido a prueba debería tener un conjunto de comportamientos estrechamente definidos". Esto responde mi pregunta (al igual que el comentario de Robert a mi pregunta) – jpoh

4

Esto parece un caso para "probar el diseño". Si el almacenamiento en caché es un detalle de implementación de SubDependency y, por lo tanto, no se puede probar directamente, probablemente deba exponerse parte de su funcionalidad (específicamente, su comportamiento de almacenamiento en caché) y, como no es natural exponerlo en SubDependency, debe ser expuesto en otra clase (llamémoslo "Caché"). En Cache, por supuesto, el comportamiento es contractual: público y, por lo tanto, comprobable.

Entonces las pruebas, y los olores, nos dicen que necesitamos una nueva clase. Diseño impulsado por prueba. ¿No es genial?

Cuestiones relacionadas