Digamos que tiene el siguiente método para probar:

public void foo(object myObject, bool myBool)
{
     if(myBool)
        repositoryA.save(myObject)
     else
        repositoryB.save(myObject)
}

¿Cuál es la mejor manera de realizar una prueba unitaria de esta función? Si escribe 2 pruebas que afirman que se llama a repositoryA cuando myBool es verdadero, y se llama a repositoryB cuando myBool es falso, entonces el siguiente cambio en la función haría que las pruebas pasen pero potencialmente rompería la funcionalidad de la aplicación:

public void foo(object myObject, bool myBool)
{
    repositoryA.save(myObject)
    repositoryB.save(myObject)
}

Por otro lado, si afirma que cuando myBool es verdadero, entonces se llama a repositoryA Y que no se llama a repositoryB, eso le da más confianza de que cualquier cambio en la función no introducirá errores, pero luego tiene una prueba que depende de la implementación. detalles. ¿Cuál es la mejor manera de hacerlo?

¿Y si usa TDD, qué pruebas escribiría para alcanzar la funcionalidad deseada?

0
Chocoman 16 oct. 2018 a las 15:24

2 respuestas

La mejor respuesta

¿Cuál es la mejor manera de realizar una prueba unitaria de esta función?

¿Empezar por rediseñarlo?

Parte del objetivo de desarrollar la prueba primero es la afirmación de que las interfaces probables también son "mejores"; más fácil de consumir, más fácil de mantener.

Entonces, el punto que (correctamente) plantea sobre probar los efectos secundarios de este método es un "olor a diseño": es una pista de que tal vez este diseño de código no es el que necesita para su caso de uso.

Dos posibilidades:

Una es que el código intenta decirle que tiene un requisito de telemetría; que debería poder consultar el sistema bajo prueba y saber cuántos objetos se han guardado en cada repositorio, o cuál fue el último objeto guardado en cada repositorio, o algo por el estilo.

Entonces puede aprovechar la telemetría para escribir su prueba

Given:
    telemetry reports that repository B has stored 7 objects
    and myBool is true
When:
    foo()
Then:
    telemetry reports that repository B has stored 7 objects

Esto básicamente rompe el problema en dos partes; una colección de pruebas que aseguran que la telemetría informa con precisión la cantidad de veces que un repositorio ha guardado el objeto, y luego una prueba de foo() que asume que la telemetría está funcionando.

Segunda alternativa: la prueba intenta decirle que desea poder evaluar qué efectos secundarios están ocurriendo en el programa. Así que convierta esos efectos en un ciudadano de primera clase en su diseño y escriba pruebas que verifiquen los efectos.

List<Effect> foo () {
    if (myBool) {
        return List.of(SaveInRepositoryA);
    } else {
        return List.of(SaveInRepositoryB);
    }
}

Ahora su afirmación es más fácil: solo asegúrese de que la Lista tenga el número correcto de elementos y los elementos correctos.

Nota importante: lo que hacen estos diseños es crear una unión entre su lógica y los efectos secundarios. Imagine un límite, en un lado del límite hay una lógica complicada que es fácil de probar, porque todo son manipulaciones de datos en la memoria; al otro lado del límite está el código que es difícil de probar, pero tan simple y directo que obviamente no tiene ningún error.

0
VoiceOfUnreason 16 oct. 2018 a las 17:40

Siguiendo su pregunta (lea también mi comentario, podría ser valioso en términos más generales), escribiría algo así: (usando JMock, una biblioteca de simulación de Java)

// with myBool == true

context.checking(new Expectations(){{
    oneOf(repositoryA).save(object);
    never(repositoryB);
}});

underTest.foo(object, true);




// with myBool == false

context.checking(new Expectations(){{
    never(repositoryA);
    oneOf(repositoryB).save(object);
}});

underTest.foo(object, false);
0
Giancarlo Di Paolantonio 16 oct. 2018 a las 17:02