¿Es apropiado burlarse de las pruebas unitarias en este escenario?

7

He escrito alrededor de 20 métodos en Java y todos ellos llaman a algunos servicios web. Ninguno de estos servicios web está disponible todavía. Para continuar con la codificación del lado del servidor, codifiqué los resultados que se espera que brinde el servicio web.

¿Podemos probar estos métodos por unidad? Hasta donde sé, las pruebas unitarias se están burlando de los valores de entrada y ver cómo responde el programa. ¿Son importantes las burlas de los valores de entrada y salida?

Editar:

Las respuestas aquí sugieren que debería estar escribiendo casos de prueba de unidad.

Ahora, ¿cómo puedo escribirlo sin modificar el código existente?

Considere el siguiente código de muestra (código hipotético):

    public int getAge()
    {
            Service s = locate("ageservice"); // line 1
            int age = s.execute(empId); // line 2
             return age; // line 3

 }

Ahora, ¿cómo nos burlamos de la salida?

En este momento, estoy comentando 'línea 1' y reemplazando la línea 2 con int age= 50. Es esto correcto ? ¿Alguien puede señalarme la forma correcta de hacerlo?

Vinoth Kumar CM
fuente

Respuestas:

12

Si.

Use simulacros y talones para simular el comportamiento esperado de los servicios web. Valide que su código funciona correctamente bajo todos los valores límite esperados y las clases de equivalencia.

EDITAR:

No, no debe editar manualmente la prueba unitaria con int age= 50. Debería usar la inyección de dependencia para poder reemplazarla fácilmente Servicecon un objeto simulado.

public int getAge(Service s)
{
    int age = s.execute(empId); // line 2
    return age; // line 3
}

La prueba de su unidad debería parecerse al siguiente pseudocódigo:

public int getAgeMinimumValueTest()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.age = 10;
    int age = uut.getAge(service);
    Assert(age == 10);
}
M. Dudley
fuente
1
Si nos estamos burlando tanto de la entrada como de la salida, ¿cuál es el punto? ¿Cómo nos burlamos de la salida en primer lugar?
Vinoth Kumar CM
1
Cuando la unidad prueba un único método, debe crear una prueba unitaria para cada caso de uso. Es decir, en cada prueba unitaria, usted especifica diferentes valores de entrada para el método y verifica que la salida sea el valor correcto. Si el método depende de un servicio web, se burla del servicio web para que se comporte como se espera en ese caso de uso. Por ejemplo, puede escribir código simulado para el servicio web para proporcionar valores que se consideran entradas al método.
M. Dudley
3

Sí, deberías burlarte de tu servicio. Burlarse es la práctica de usar objetos simulados para reemplazar los objetos reales que normalmente usarías en tiempo de ejecución. La mayoría de las veces desearía utilizar un marco de imitación para crear objetos simulados de forma fácil y dinámica.

Si usa objetos simulados, probablemente escribiría un código que se parecería más a esto:

public int getAge(ServiceInterface service)
{
    return service.execute(empId);
}

¿Dónde serviceestá el servicio real que va a utilizar en tiempo de ejecución, pero durante una prueba unitaria es un objeto simulado con comportamiento simulado? Observe que el parámetro para getAgeya no es una clase concreta, sino que es una interfaz. Esto le permite usar cualquier objeto como parámetro siempre que implemente la interfaz, en lugar de solo una clase específica. Su prueba unitaria tomaría los siguientes pasos:

  1. Crea el objeto simulado
  2. Dile que regrese una cierta edad cuando executese llama
  3. Llamar getAgecon el objeto simulado como parámetro
  4. Verificar getAgedevuelve la edad correcta.

Para obtener una buena lista de frameworks de simulación de Java, consulte esta pregunta de stackoverflow .

EDITAR

No comente su código y reemplácelo con una constante solo para sus pruebas unitarias. El problema con esto es que tendrá que asegurarse de que se comente lo correcto cuando se ejecutan las pruebas unitarias, y lo correcto se comentará cuando se lo envíe al cliente. Esto se convertiría en una pesadilla para fines de mantenimiento a largo plazo, especialmente si tiene la intención de escribir más de 2 pruebas unitarias que requieren simulacros. Aparte de eso, sus pruebas deben ejecutarse contra el producto terminado ; si no, eso significa que no está probando el código real que se está lanzando, lo que niega el propósito de tener pruebas en primer lugar.

Phil
fuente
1

Sí lo es. Su trabajo es demostrar que su código hace lo correcto, dado su entorno. Los servicios web externos son parte del entorno; No es su trabajo probar su correcto funcionamiento, es el trabajo de las personas que escriben los servicios. La asignación de entrada / salida que las pruebas deben verificar es la entrada / salida a su código; si el código produce información para que otro colaborador haga su trabajo, eso es estrictamente incidental.

De hecho, incluso después de que los servicios web estén disponibles, probablemente sea una buena idea mantener sus pruebas unitarias utilizando un entorno simulado en lugar del real. Hace que las pruebas sean más simples, menos frágiles con respecto al contexto del sistema esperado y probablemente también más rápidas.

Kilian Foth
fuente
"Probablemente una buena idea"? Yo diría esencial. No puede garantizar pruebas unitarias confiables a través de los límites del sistema.
thehowler
1

Para agregar a la excelente respuesta de emddudley, la mayor ganancia que puede obtener al burlarse del servicio es poder probar lo que debería suceder si el servicio no funciona correctamente. El pseudocódigo de prueba podría verse así:

public int AgeMinimumValue_LogsServiceError_Test()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    try {
        int age = uut.getAge(service, logger);
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

Y ahora su implementación ha sido editada con este nuevo requisito

public int getAge(Service s, Logger l)
{
    try {
        int age = s.execute(empId);
        return age;
    }
    catch(Exception ex) {
        l.LogError(ex);
        throw;
    }
}

En otros escenarios, es más probable que necesite responder a respuestas más complicadas. Si el servicio proporcionó el procesamiento de la tarjeta de crédito, deberá responder a Éxito, Servicio no disponible, Tarjeta de crédito vencida, Número no válido, etc. Al burlarse del servicio, puede asegurarse de responder a estos escenarios de una manera adecuada para su situación. En este caso, debe burlarse de la entrada / salida del servicio y la retroalimentación que obtiene al saber que el código de consumo funcionará para todas las salidas conocidas es realmente significativa y valiosa.

EDITAR: Acabo de notar que quieres poder burlarte sin modificar el método existente. Para hacer esto, el locate("ageservice");método debería cambiarse para admitir objetos simulados en las pruebas y localizar el servicio real una vez que esté listo. Esta es una variación del patrón del localizador de servicios que abstrae la lógica para recuperar la implementación del servicio que está utilizando. Una versión rápida de eso puede verse así:

public Service locate(string serviceToLocate) {
    if(testEnvironment) // Test environment should be set externally
        return MockService(serviceToLocate);
    else
        return Service(serviceToLocate);
}

Sin embargo, mi recomendación sería mover las dependencias del servicio a los Constructores:

public int AgeMinimumValue_LogsServiceError_Test()
{
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    ClassUnderTest uut = new ClassUnderTest(service, logger);

    try {
        int age = uut.getAge();
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

Ahora, el método getAge ya no tiene la responsabilidad de buscar el servicio, ya que se ha extraído completamente de la clase dejando una implementación similar a esta:

public int getAge()
{
    try {
        // _service is a private field set by the constructor
        int age = _service.execute(empId); 
        return age;
    }
    catch(Exception ex) {
         // _logger is a private field set by the constructor
        _logger.LogError(ex);
        throw;
    }
}
Phil Patterson
fuente
0

¿Son importantes las burlas de los valores de entrada y salida?

No, pero no te estás burlando del valor de entrada. Su prueba se está ejecutando y el servicio simulado verifica que se acceda a la parte correcta del contrato. Que usted devuelva un valor particular es irrelevante.

Por otro lado, si su método tiene alguna lógica, entonces el valor de retorno es importante. Aquí se burla de las entradas a la lógica (el resultado del servicio web) y prueba la salida.

Telastyn
fuente
0

Ahora, ¿cómo puedo escribirlo sin modificar el código existente?

no puedes Pero puede crear una interfaz "IMyService" con la que puede programar que contenga todas las firmas y métodos de servicio web.

public int getAge()
{
        IMyService s = getService(); 
        int age = s.execute(empId);
         return age; // line 3

}

En el modo Producción, getService();se devolverá una referencia a un servicio web funcional completo y, en el modo de prueba, una implementación alternativa (o simulación) que devuelva sus datos falsos.

k3b
fuente
0

La burla no se trata de entradas o salidas, sino de reemplazar dependencias externas. Entonces sí, es apropiado escribir pruebas unitarias y simular los servicios web externos.

Ahora para las malas noticias: dependiendo del idioma y las herramientas disponibles para usted, es posible que no pueda burlarse de las dependencias externas a menos que el código haya sido diseñado para permitírselo. Si este es el caso, sus pruebas se convertirán en pequeñas pruebas de integración en lugar de pruebas unti puras.

En el lado positivo, parece que está permitido modificar el código (de lo contrario, ¿cómo lo está comentando?), Pase una interfaz a su servicio para permitir que se burle (o use alguna otra forma de inyección de dependencia )

Finalmente, lo que desea probar parece ser una envoltura pura alrededor del servicio externo, no hay casi nada para probar la unidad aquí aparte de que llame al servicio. Como se trata fundamentalmente de una interfaz, la mayoría de las pruebas deberán realizarse más tarde a un nivel superior (pruebas de integración)

jk.
fuente