Únase a nosotros el 30 de abril: Presentación de la prueba CT de Parasoft C/C++ para pruebas continuas y excelencia en el cumplimiento | Regístrese ahora

Burlarse en Java: cómo automatizar una prueba unitaria de Java, incluidas las burlas y las afirmaciones

Foto de cabeza de Brian McGlauflin,
19 de Julio de 2018
6 min leer

Al reducir la complejidad involucrada con la burla, Parasoft Jtest le permite escribir pruebas unitarias de forma rápida y sencilla. Obtenga más información sobre cómo automatizar una prueba unitaria de Java, incluidas las burlas y las afirmaciones.

Bueno pruebas unitarias son una excelente manera de asegurarse de que su código funcione hoy y continúe funcionando en el futuro. Un conjunto completo de pruebas, con una buena cobertura basada en código y en comportamiento, puede ahorrarle a una organización mucho tiempo y dolores de cabeza. Y, sin embargo, no es raro ver proyectos en los que no se escriben suficientes pruebas. De hecho, algunos desarrolladores incluso han argumentado completamente en contra de su uso.

¿Qué hace una buena prueba unitaria?

Hay muchas razones por las que los desarrolladores no escriben suficientes pruebas unitarias. Una de las principales razones es la cantidad de tiempo que tardan en construir y mantener, especialmente en proyectos grandes y complejos. En proyectos complejos, a menudo una prueba unitaria necesita instanciar y configurar muchos objetos. Esto lleva mucho tiempo configurarlo y puede hacer que la prueba sea tan compleja (o más compleja) que el código que está probando.

Veamos un ejemplo en Java:

público LoanResponse requestLoan (LoanRequest solicitud de préstamo, estrategia de préstamo estrategia)
{     LoanResponse response = new LoanResponse();     response.setApproved(true);     if (loanRequest.getDownPayment().compareTo(loanRequest.getAvailableFunds()) > 0) {        response.setApproved(false);        response.setMessage("error.insufficient.funds.for.down.payment");        return response;     }     if (strategy.getQualifier(loanRequest) < strategy.getThreshold(adminManager)) {         response.setApproved(false);         response.setMessage(getErrorMessage());     }     return response; }

Aquí tenemos un método que procesa un Solicitud de préstamo, generando un PréstamoRespuesta. Nota la Estrategia de préstamo argumento, que se utiliza para procesar el Solicitud de préstamo. El objeto de estrategia puede ser complejo: puede acceder a una base de datos, un sistema externo o lanzar un Excepción en tiempo de ejecución. Para escribir una prueba para pedir prestamo(), Necesito preocuparme por el tipo de Estrategia de préstamo Estoy probando y probablemente necesite probar mi método con una variedad de Estrategia de préstamo implementaciones y Solicitud de préstamo configuraciones.

Una prueba unitaria parapedir prestamo()puede verse así:

@Test public void testRequestLoan() throws Throwable {    // Set up objects    DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();    LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000);    LoanStrategy strategy = new AvailableFundsLoanStrategy();    AdminManager adminManager = new AdminManagerImpl();    underTest.setAdminManager(adminManager);    Map<String, String> parameters = new HashMap<>();    parameters.put("loanProcessorThreshold", "20");    AdminDao adminDao = new InMemoryAdminDao(parameters);    adminManager.setAdminDao(adminDao);    // Call the method under test    LoanResponse response = processor.requestLoan(loanRequeststrategy);    // Assertions and other validations 

Como puede ver, hay una sección completa de mi prueba que solo crea objetos y configura parámetros. No era obvio mirar el pedir prestamo() método qué objetos y parámetros deben configurarse. Para crear este ejemplo, tuve que ejecutar la prueba, agregar alguna configuración, luego volver a ejecutar y repetir el proceso una y otra vez. Tuve que dedicar demasiado tiempo a averiguar cómo configurar el Gerente de administración y Estrategia de préstamo en lugar de centrarme en mi método y en lo que había que probar allí. Y todavía necesito ampliar mi prueba para cubrir más Solicitud de préstamo casos, más estrategias y más parámetros para Admindao.

Además, al usar objetos reales para probar, mi prueba en realidad está validando más que solo el comportamiento de pedir prestamo() - Estoy dependiendo del comportamiento de FondosDisponiblesPréstamoEstrategia, Adminmanagerimply Admindao para que se ejecute mi prueba. Efectivamente, también estoy probando esas clases. En algunos casos, esto es deseable, pero en otros casos no lo es. Además, si una de esas otras clases cambia, la prueba puede comenzar a fallar aunque el comportamiento de pedir prestamo() no cambió. Para esta prueba, preferimos aislar la clase bajo prueba de sus dependencias.

¿Qué es la burla en Java?

Una solución al problema de la complejidad es burlarse de esos objetos complejos. Para este ejemplo, comenzaré usando un simulacro para el Estrategia de préstamo parámetro:

@Prueba

vacío público testRequestLoan () tiros Lanzable {
    // Configurar objetos 
    Procesador de Préstamo de Pago Inicial procesador = nueva DownPaymentLoanProcessor (); Solicitud de préstamo solicitud de préstamo = LoanRequestFactory.create (1000, 100, 10000); LoanStrategy estrategia = Mockito.mock (LoanStrategy.clase); Mockito. Cuando (estrategia.getQualifier (cualquier (LoanRequest.clase))). luegoReturn (20.0d); Mockito. Cuando (estrategia.getThreshold (cualquier (AdminManager.clase))). luegoReturn (20.0d);

    // Llamar al método bajo prueba
    PréstamoRespuesta respuesta = procesador.requestLoan (solicitud de préstamoestrategia);

    // Afirmaciones y otras validaciones
}

Veamos lo que está sucediendo aquí. Creamos una instancia simulada de Estrategia de préstamo usando Mockito.mock (). Ya que sabemos que getQualifier () y getThreshold () será llamado en la estrategia, definimos los valores de retorno para esas llamadas usando Mockito.Cuando (…) .entoncesReturn (). Para esta prueba, no nos importa lo que Solicitud de préstamo los valores de la instancia son, ni necesitamos un valor real Gerente de administración más porque Gerente de administración solo fue usado por el real Estrategia de préstamo.

Además, dado que no estamos usando un Estrategia de préstamo, no nos importa cuáles sean las implementaciones concretas de Estrategia de préstamo podría hacer. No necesitamos configurar entornos de prueba, dependencias u objetos complejos. Estamos enfocados en probar pedir prestamo() - no Estrategia de préstamo or Gerente de administración. El código de flujo del método bajo prueba es controlado directamente por el simulacro.

Esta prueba es mucho más fácil de escribir con Mockito de lo que hubiera sido si tuviera que crear un complejo Estrategia de préstamo ejemplo. Pero todavía hay algunos desafíos:

  • Para aplicaciones complejas, las pruebas pueden requerir muchos simulacros
  • Si es nuevo en Mockito, necesita aprender su sintaxis y patrones
  • Es posible que no sepa qué métodos deben burlarse
  • Cuando la aplicación cambia, las pruebas (y simulaciones) también deben actualizarse

Resolver desafíos burlón con un generador de pruebas unitarias de Java

Diseñamos Parasoft Jtest para ayudar a abordar los desafíos anteriores. El módulo de prueba unitaria Prueba J de Parasoft, una solución empresarial para pruebas de Java que ayuda a los desarrolladores a gestionar los riesgos de desarrollo de softwarejava.

En el lado de las pruebas unitarias, Parasoft Jtest lo ayuda a automatizar algunas de las partes más difíciles de crear y mantener pruebas unitarias con simulacros. Para el ejemplo anterior, puede generar automáticamente una prueba para pedir prestamo() con un solo clic de botón, incluidas todas las burlas y validaciones que ve en la prueba de ejemplo.


Aquí, utilicé la acción "Regular" en Parasoft Jtest Asistente de prueba unitaria Barra de herramientas para generar la siguiente prueba:

@Test public void testRequestLoan() throws Throwable {     // Given     DownPaymentLoanProcessor underTest = new DownPaymentLoanProcessor();     // When doble fondos disponibles = 0.0d// UTA: valor predeterminado doble depósito = 0.0d// UTA: valor predeterminado doble monto del préstamo = 0.0d// UTA: valor predeterminado     LoanRequest loanRequest = LoanRequestFactory.create(availableFundsdownPaymentloanAmount);     LoanStrategy strategy = mockLoanStrategy();     LoanResponse result = underTest.requestLoan(loanRequeststrategy);    // Then    // assertNotNull(result); }

Todas las burlas de esta prueba ocurren en un método auxiliar:

estática privada LoanStrategy mockLoanStrategy () tiros {LoanStrategy desechable estrategia = simulacro (LoanStrategy.clase);
    doble obtenerResultadoCalificador = 0.0d; // UTA: valor predeterminado
    cuando(estrategia.getQualifier (cualquier (LoanRequest.clase))). luegoReturn (obtenerResultadoCalificador);

    doble obtenerResultadoUmbral = 0.0d; // UTA: valor predeterminado
    cuando(estrategia.getThreshold (cualquier (AdminManager.clase))). luegoReturn (obtenerResultadoUmbral);

    volvemos estrategia; }

Todas las burlas necesarias están configuradas para mí: Parasoft Jtest detectó las llamadas al método a getQualifier () y getThreshold () y se burló de los métodos. Una vez que configuro los valores en mi prueba unitaria para fondos disponibles, depósito, etc., la prueba está lista para ejecutarse (¡también podría generar una prueba parametrizada para una mejor cobertura!). Tenga en cuenta también que el asistente proporciona una guía sobre qué valores cambiar mediante sus comentarios, "UTA: valor predeterminado", lo que facilita las pruebas.

Esto ahorra mucho tiempo en la generación de pruebas, especialmente si no sé qué necesita ser burlado o cómo usar la API de Mockito.

Manejo de cambios de código

Cuando la lógica de la aplicación cambia, las pruebas a menudo también deben cambiar. Si la prueba está bien escrita, debería fallar si actualiza el código sin actualizar la prueba. A menudo, el mayor desafío al actualizar la prueba es comprender qué se debe actualizar y cómo realizar exactamente esa actualización. Si hay muchas simulaciones y valores, puede ser difícil rastrear cuáles son los cambios necesarios.

Para ilustrar esto, hagamos algunos cambios en el código bajo prueba:

público LoanResponse requestLoan (LoanRequest solicitud de préstamo, estrategia de préstamo estrategia) { ... Cuerda resultado = estrategia.validar(solicitud de préstamo);
    if (resultado != nulo &&!resultado.esta vacio()) {
        respuesta.setApproved (false);
        respuesta.setMessage (resultado);
        volvemos respuesta; } ...
    volvemos respuesta; }

Hemos agregado un nuevo método a LoanStrategy - validar (), y ahora lo están llamando desde pedir prestamo(). Es posible que sea necesario actualizar la prueba para especificar qué validar() debería volver.

Sin cambiar la prueba generada, ejecutémosla dentro del Asistente de prueba unitaria de Parasoft Jtest:

Parasoft Jtest detectó que validar() fue llamado a los burlados Estrategia de préstamo argumento durante mi ejecución de prueba. Dado que el método no ha sido configurado para el simulacro, el asistente recomienda que yo simule el validar() método. La acción de solución rápida "Mock it" actualiza la prueba automáticamente. Este es un ejemplo simple, pero para el código complejo en el que no es fácil encontrar el simulacro que falta, la recomendación y la solución rápida pueden ahorrarnos mucho tiempo de depuración.

Después de actualizar la prueba usando la solución rápida, puedo ver el nuevo simulacro y establecer el valor deseado para validarResultado:

estática privada LoanStrategy mockLoanStrategy () tiros {LoanStrategy desechable estrategia = simulacro (LoanStrategy.clase); Cuerda validarResultado = ""// UTA: valor predeterminado
    cuando(estrategia.validate (cualquier (LoanRequest.clase))). luegoReturn (validarResultado);
    doble obtenerResultadoCalificador = 20.0 d; cuando(estrategia.getQualifier (cualquier (LoanRequest.clase))). luegoReturn (obtenerResultadoCalificador);

    doble obtenerResultadoUmbral = 20.0 d; cuando(estrategia.getThreshold (cualquier (AdminManager.clase))). luegoReturn (obtenerResultadoUmbral);
    volvemos estrategia; }

Puedo configurar validateResult con un valor no vacío para probar el caso de uso donde el método ingresa al nuevo bloque de código, o puedo usar un valor vacío (o nulo) para validar el comportamiento cuando no se ingresa el nuevo bloque.

Analizar el flujo de prueba

El asistente también proporciona algunas herramientas útiles para analizar el flujo de prueba. Por ejemplo, aquí está el árbol de flujo para nuestra ejecución de prueba:

El Parasoft Jtest Asistente de prueba unitariaÁrbol de flujo, que muestra las llamadas realizadas durante la ejecución de la prueba

Cuando se ejecutó la prueba, puedo ver que la prueba creó un nuevo simulacro para Estrategia de préstamo, y se burló del validar(), getQualifier ()y getThreshold () métodos. Puedo seleccionar llamadas a métodos y ver (en la vista Variables) qué argumentos se enviaron a esa llamada y qué valor se devolvió (o excepciones arrojadas). Al depurar pruebas, esto puede ser mucho más fácil de usar y comprender que buscar en archivos de registro.

Resumen

De modo que puede automatizar muchos aspectos de las pruebas unitarias. Parasoft Jtest lo ayuda a generar pruebas unitarias con menos tiempo y esfuerzo, lo que lo ayuda a reducir la complejidad asociada con las burlas. También hace muchos otros tipos de recomendaciones para mejorar las pruebas existentes basadas en datos de tiempo de ejecución y tiene soporte para pruebas parametrizadas, pruebas de aplicaciones Spring y PowerMock (para burlarse de métodos y constructores estáticos). Inicie su prueba Jtest de 14 días con todas las funciones .

Mejore las pruebas unitarias para Java con automatización