X
BLOG

Me encantan las pruebas de primavera aún más con Mocking y Unit Test Assistant

Me encantan las pruebas de primavera aún más con Mocking y Unit Test Assistant Tiempo de leer: 7 minutos
Al Marco de primavera (junto con Spring Boot) proporciona un marco de prueba útil para escribir pruebas JUnit para sus controladores Spring.

En mi Publicación anterior, hablamos sobre cómo construir y mejorar estas pruebas de manera eficiente con Parasoft Jtest's Asistente de prueba unitaria. En esta publicación, continuaré abordando uno de los mayores desafíos de probar cualquier aplicación compleja: gestión de la dependencia.

¿Por qué necesito burlarse?

Seamos honestos. Las aplicaciones complejas no se crean desde cero: utilizan bibliotecas, API y proyectos o servicios centrales que otra persona crea y mantiene. Como desarrolladores de Spring, aprovechamos la funcionalidad existente tanto como sea posible para que podamos dedicar nuestro tiempo y esfuerzo a lo que nos importa: la lógica comercial de nuestra aplicación. Dejamos los detalles a las bibliotecas, por lo que nuestras aplicaciones tienen muchas dependencias, que se muestran en naranja a continuación:

Fig. 1. Un servicio Spring con múltiples dependencias

Entonces, ¿cómo enfoco las pruebas unitarias en mi aplicación (controlador y servicio) si la mayor parte de su funcionalidad depende del comportamiento de estas dependencias? ¿No estoy, al final, realizando siempre pruebas de integración en lugar de pruebas unitarias? ¿Qué sucede si necesito un mejor control sobre el comportamiento de esas dependencias o las dependencias no están disponibles durante las pruebas unitarias?

Mejore las pruebas unitarias para Java con automatización: mejores prácticas para desarrolladores de Java

Lo que necesito es una forma de aislar mi aplicación de esas dependencias, para poder enfocar mis pruebas unitarias en el código de mi aplicación. En algunos casos, podríamos crear versiones de "prueba" especializadas de estas dependencias. Sin embargo, el uso de una biblioteca estandarizada como Mockito proporciona beneficios sobre este enfoque por múltiples razones:

  • No tiene que escribir y mantener el código especial de "prueba" usted mismo
  • Las bibliotecas simuladas pueden rastrear invocaciones contra simulacros, proporcionando una capa adicional de validación
  • Las bibliotecas estándar como PowerMock brindan funcionalidad adicional, como burlarse de métodos estáticos, métodos privados o constructores
  • El conocimiento de una biblioteca simulada como Mockito se puede reutilizar en todos los proyectos, mientras que el conocimiento del código de prueba personalizado no se puede reutilizar.

Fig. 2. Un servicio simulado reemplaza múltiples dependencias

Dependencias en Spring

En general, las aplicaciones Spring dividen la funcionalidad en Beans. Un controlador puede depender de un Bean de servicio y el Bean de servicio puede depender de un EntityManager, una conexión JDBC u otro Bean. La mayoría de las veces, las dependencias de las que se debe aislar el código bajo prueba son beans. En una prueba de integración, tiene sentido que todas las capas sean reales, pero para las pruebas unitarias, debemos decidir qué dependencias deben ser reales y cuáles deben ser simuladas.

Spring permite a los desarrolladores definir y configurar beans utilizando XML, Java o una combinación de ambos para proporcionar una mezcla de beans simulados y reales en su configuración. Dado que los objetos simulados deben definirse en Java, se debe utilizar una clase de configuración para definir y configurar los beans simulados.

Burlarse de las dependencias

Cuando UTA genera una prueba de Spring, todas las dependencias de su controlador se configuran como simulaciones para que cada prueba obtenga el control sobre la dependencia. Cuando se ejecuta la prueba, UTA detecta las llamadas a métodos realizadas en un objeto simulado para métodos que aún no tienen configurado el simulacro de método, y recomienda que esos métodos se simulen. Luego, podemos usar una solución rápida para simular automáticamente cada método.

Aquí hay un controlador de ejemplo que depende de un PersonService:

@Controller
@RequestMapping("/people")
public class PeopleController {
 
    @Autowired
    protected PersonService personService;
    @GetMapping
    public ModelAndView people(Model model){
   
        for (Person person : personService.getAllPeople()) {
            model.addAttribute(person.getName(), person.getAge());
        }
        return new ModelAndView("people.jsp", model.asMap());
    }
}

 

Y una prueba de ejemplo, generada por el asistente de pruebas unitarias de Parasoft Jtest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PeopleControllerTest {
 
    @Autowired
    PersonService personService;
 
    // Other fields and setup
 
    @Configuration
    static class Config {
 
        // Other beans
 
        @Bean
        public PersonService getPersonService() {
            return mock(PersonService.class);
        }
    }
 
    @Test
    public void testPeople() throws Exception {
        // When
        ResultActions actions = mockMvc.perform(get("/people"));
    }
}

 

Aquí, la prueba usa una clase interna anotada con @Configuración, que proporciona dependencias de beans para el controlador bajo prueba utilizando la configuración de Java. Esto nos permite burlarnos del PersonService en el método del frijol. Todavía no hay métodos simulados, por lo que cuando ejecuto la prueba, veo la siguiente recomendación:

Esto significa que el getAllPeople () se invocó el método en mi burla PersonService, pero la prueba aún no configura la simulación para este método. Cuando elijo la opción de corrección rápida "Mock it", la prueba se actualiza:

    @Test
    public void testPeople() throws Exception {
        Collection<Person> getAllPeopleResult = new ArrayList<Person>();
        doReturn(getAllPeopleResult).when(personService).getAllPeople();
        // When
        ResultActions actions = mockMvc.perform(get("/people"));

Cuando vuelvo a ejecutar la prueba, pasa. Todavía debería poblar el Colección que es devuelto por getAllPeople (), pero el desafío de configurar mis dependencias simuladas está resuelto.

Tenga en cuenta que podría mover el método generado de burla del método de prueba al método de frijol de la clase de configuración. Si hago esto, significa que cada prueba en la clase simulará el mismo método de la misma manera. Dejar el método burlándose en el método de prueba significa que el método puede ser burlado de manera diferente entre diferentes pruebas.

Bota de primavera

Spring Boot hace que burlarse de los frijoles sea aún más fácil. En lugar de usar un @Autowired campo para el bean en la prueba y una clase de configuración que lo define, simplemente puede usar un campo para el bean y anotarlo con @MockBean. Spring Boot creará un simulacro para el bean usando el marco simulado que encuentra en la ruta de clases, y lo inyectará de la misma manera que se puede inyectar cualquier otro bean en el contenedor.

Acelere las pruebas unitarias de aplicaciones Spring con Parasoft Jtest y su asistente de pruebas unitarias

Al generar pruebas de Spring Boot con Unit Test Assistant, el @MockBean se utiliza la funcionalidad en lugar de la clase de configuración.

@SpringBootTest
@AutoConfigureMockMvc
public class PeopleControllerTest {
    // Other fields and setup – no Configuration class needed!
 
    @MockBean
    PersonService personService;
 
    @Test
    public void testPeople() throws Exception {
        ...
    }
}

Configuración XML vs Java

En el primer ejemplo anterior, la clase Configuration proporcionó todos los beans al contenedor Spring. Como alternativa, puede utilizar la configuración XML para la prueba en lugar de la clase Configuración; o puedes combinar los dos. Por ejemplo:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/**/testContext.xml" })
public class PeopleControllerTest {
 
    @Autowired
    PersonService personService;
 
    // Other fields and setup
 
    @Configuration
    static class Config {
        @Bean
        @Primary
        public PersonService getPersonService() {
            return mock(PersonService.class);
        }
    }
 
    // Tests
}

 

Aquí, la clase hace referencia a un archivo de configuración XML en el @ContextConfiguration anotación (no se muestra aquí) para proporcionar la mayoría de los beans, que podrían ser beans reales o beans específicos de la prueba. También proporcionamos un @Configuración clase, donde PersonService se burla. los @Primario anotación indica que incluso si una PersonService bean se encuentra en la configuración XML, esta prueba utilizará el bean simulado de la @Configuración clase en su lugar. Este tipo de configuración puede hacer que el código de prueba sea más pequeño y más fácil de administrar.

Puede configurar UTA para generar pruebas utilizando cualquier @ContextConfiguration atributos que necesita.

Burlarse de métodos estáticos

A veces, se accede a las dependencias de forma estática. Por ejemplo, una aplicación puede acceder a 3rd-servicio de fiesta a través de una llamada a método estático:

public class ExternalPersonService {
    public static Person getPerson(int id) {
       RestTemplate restTemplate = new RestTemplate();
       try {
           retorno restTemplate.getForObject("http://domain.com/people/" + id, Person.class);
        } catch (RestClientException e) {
            return null;
        }
    }
}

 

En nuestro controlador:

    @GetMapping
    public ResponseEntity<Person> getPerson(@PathVariable("id") int id, Model modelo)
    {
        Person person = ExternalPersonService.getPerson(id);
        if (person != null) {
            return new ResponseEntity<Person>(person, HttpStatus.OK);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

 

En este ejemplo, nuestro método controlador usa una llamada a un método estático para obtener un objeto Person de un 3rd-servicio de fiesta. Cuando construimos una prueba JUnit para este método de controlador, se realizará una llamada HTTP real al servicio cada vez que se ejecute la prueba.

En cambio, burlémonos de la estática ExternalPersonService.getPerson () método. Esto evita la llamada HTTP y nos permite proporcionar un Persona respuesta de objeto que se adapte a nuestras necesidades de prueba. Unit Test Assistant puede facilitar la simulación de métodos estáticos con PowerMockito.

UTA genera una prueba para el método del controlador anterior que se ve así:

    @Test
    public void testGetPerson() throws Throwable {
        // When
        long id = 1L;
        ResultActions actions = mockMvc.perform(get("/people/" + id));
 
        // Then
        actions.andExpect(status().isOk());
    }

 

Cuando ejecutamos la prueba, veremos que se realiza la llamada HTTP en el árbol de flujo de UTA. Busquemos la llamada a ExternalPersonService.getPerson () y burlarse de él en su lugar:

 

 

La prueba se actualiza para simular el método estático para esta prueba usando PowerMock:

    @Test
    public void testGetPerson() throws Throwable {
        spy(ExternalPersonService.class);
 
        Person getPersonResult = null; // UTA: default value
        doReturn(getPersonResult).when(ExternalPersonService.class, "getPerson", anyInt());
 
        // When
        int id = 0;
        ResultActions actions = mockMvc.perform(get("/people/" + id));
 
        // Then
        actions.andExpect(status().isOk());
    }

 

Usando UTA, ahora podemos seleccionar el getPersonResult variable e instanciarla, de modo que la llamada al método simulado no regrese nulo:

    String name = ""; // UTA: default value
    int age = 0; // UTA: default value
    Person getPersonResult = new Person(name, age);

 

Cuando volvamos a ejecutar la prueba, getPersonResult se devuelve de la burlaExternalPersonService.getPerson () método, y la prueba pasa.

Nota: Desde el árbol de flujo, también puede elegir "Agregar patrón de método simulable" para las llamadas a métodos estáticos. Esto configura Unit Test Assistant para simular siempre esas llamadas a métodos estáticos al generar nuevas pruebas.

Conclusión

Las aplicaciones complejas a menudo tienen dependencias funcionales que complican y limitan la capacidad de un desarrollador para realizar pruebas unitarias de su código. El uso de un marco de simulación como Mockito puede ayudar a los desarrolladores a aislar el código bajo prueba de estas dependencias, permitiéndoles escribir mejores pruebas unitarias más rápidamente. los Parasoft Jtest Asistente de prueba unitaria facilita la gestión de dependencias configurando nuevas pruebas para usar simulacros, y encontrando simulacros de métodos faltantes en tiempo de ejecución y ayudando a los desarrolladores a generar simulacros para ellos.

Automatice la creación de pruebas de Junit con Parasoft Jtest y comience a amar las pruebas unitarias.
Solicite una demostración ahora
Escrito por

Brian McGlauflin

Brian McGlauflin es un ingeniero de software en Parasoft con experiencia en el desarrollo de pila completa utilizando Spring y Android, pruebas de API y virtualización de servicios. Actualmente se centra en las pruebas de software automatizadas para aplicaciones Java con Parasoft Jtest.

Reciba las últimas noticias y recursos sobre pruebas de software en su bandeja de entrada.

Prueba Parasoft