Try   HackMD

Guía de desarrollo de pruebas unitarias

  • Coordinador Tecnológico: Rafael Palau
  • Arquitecto: Marcelo Lopez
  • Consultores:
    Ilse Grau
    Julio Mello
    Marco Aquino
    Marcos Benítez
    Lauro Segovia
    Alan Sanier

Contenido

Introducción

La guía de de pruebas unitarias describe los pasos necesarios para la elaboración y ejecución de las misma.

Objetivo

  • Diseñar e implementar pruebas unitarias de los servicios de backend.

Resultados esperados

Pruebas unitarias implementadas para los servicios de backend.

Pruebas de software

Pruebas basadas en la ejecución de código

El objetivo de las pruebas es descubrir los errores y fallos cometidos durante las fases de desarrollo del software [1].

Pruebas de caja blanca

Las pruebas de caja blanca se centran en probar el comportamiento interno y la estructura del programa examinando la lógica interna, como muestra la Figura 1. Para ello:

  • Se ejecutan todas las sentencias (al menos una vez).
  • Se recorren todos los caminos independientes de cada módulo.
  • Se comprueban todas las decisiones lógicas.
  • Se comprueban todos los bucles.
    Finalmente, en todos los casos se intenta provocar situaciones extremas o límites.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figura 1. Lógica interna de un programa [1].

Pruebas de caja negra

La conducción de las pruebas de caja negra están determinadas por los datos de entrada y salida. Así, los datos de entrada deben generar una salida en concordancia con las especificaciones considerando el software como una caja negra sin tener en cuenta los detalles procedimentales de los programas.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figura 2. Esquema de pruebas de caja negra [1].

Estrategias de pruebas

La detección de errores tendra un costo mayor cuanto más tarde se detecten. Por tal motivo, es importante realizar comprobaciones de los resultados generados en cada fase del ciclo de vida del proyecto de software antes de pasar a la siguiente fase [1].

Tipos de estrategias de pruebas:

  • Pruebas unitarias. Se comprueba la lógica, funcionalidad y la especificación de cada módulo o clase aisladamente respecto del resto de módulos o clases.
  • Pruebas de integración. Se tiene en cuenta la agrupación de módulos o clases y el flujo de información entre ellos a través de las interfaces.
  • Pruebas de validación. Se comprueba la concordancia respecto a los requisitos de usuario y software.
  • Pruebas del sistema. Se integra el sistema software desarrollado con su entorno hardware y software.
  • Pruebas de aceptación. El usuario valida que el producto se ajuste a los requisitos del usuario.

Pruebas unitarias

jUnit 5

Junit, es el un framework que nos permite escribir y ejecutar pruebas unitarias en Java.

JUnit 5 inicialmente se llamo JUnit Lambda y está completamente reescrito en Java 8. Está rediseñado desde cero, superando los errores y limitaciones de las versiones anteriores. JUnit 5 es compatible con las versiones anteriores, por lo que también puedes ejecutar tus antiguas pruebas JUnit con él [2].

Mockito

Mockito, es un framework de código abierto, que nos permite la creación de objetos simulados, con el propósito de realizar pruebas unitarias en Java.

Estructura del proyecto

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Figura 3. Estructura del proyecto. Fuente: Equipo de Desarrollo

Ejemplo

A continuación se puede observa el código fuente de la clase MedidaServiceImplTest.java.

package py.gov.siare.presupuesto.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;

import py.gov.siare.presupuesto.dto.MedidaDto;
import py.gov.siare.siarecommon.dto.ResponseDto;

@DisplayName("Test MedidaService")
@TestMethodOrder(OrderAnnotation.class)
@TestInstance(Lifecycle.PER_CLASS)
class MedidaServiceImplTest {
	
	private MedidaService service;
	

	@BeforeEach
	public void init() {
		this.service = mock(MedidaService.class);
	}
	
	@ParameterizedTest
	@Order(1)
	@MethodSource("getValues")
	public void testGuardarRegistro(MedidaDto medidaDto) {


		ResponseDto<MedidaDto> respuestaEsperada = new ResponseDto<MedidaDto>();
		respuestaEsperada.setRespuesta(medidaDto);

		when(service.guardar(medidaDto)).thenReturn(respuestaEsperada);

		ResponseDto<MedidaDto> respuesta = this.service.guardar(medidaDto);

		assertThat(respuestaEsperada.getRespuesta().getNombre()).isEqualTo(respuesta.getRespuesta().getNombre());
	}
	
	@ParameterizedTest
	@Order(2)
	@MethodSource("getValues")
	public void testObtenerListadoFiltradoPaginado(MedidaDto medidaDto) {

		String filtros = "{\"id\": \"1\"}";

		List<MedidaDto> listadoMedidaDto = new ArrayList<>();
		listadoMedidaDto.add(medidaDto);

		ResponseDto<List<MedidaDto>> respuestaEsperada = new ResponseDto<List<MedidaDto>>();
		respuestaEsperada.setRespuesta(listadoMedidaDto);

		when(this.service.obtenerRegistrosPaginadoOrdenado(filtros, 0, 10, null, null)).thenReturn(respuestaEsperada);

		ResponseDto<List<MedidaDto>> respuesta = this.service.obtenerRegistrosPaginadoOrdenado(filtros, 0, 10, null,
				null);

		assertThat(respuesta.getRespuesta()).hasSize(1);
	}

	@Test
	@Order(3)
	public void testObtenerListadoFiltradoPorId() {

		MedidaDto medidaDto = new MedidaDto();
		medidaDto.setId(2L);
		
		ResponseDto<MedidaDto> respuestaEsperada = new ResponseDto<>();
		respuestaEsperada.setRespuesta(medidaDto);

		when(this.service.obtenerPorId(Mockito.anyLong())).thenReturn(respuestaEsperada);

		ResponseDto<MedidaDto> respuesta = this.service.obtenerPorId(medidaDto.getId());

		assertThat(medidaDto.getId()).isEqualTo(respuesta.getRespuesta().getId());
	}
	
	@ParameterizedTest
	@Order(4)
	@MethodSource("getValues")
	public void testActualizarRegistro(MedidaDto medidaDto) {

		medidaDto.setAbreviatura("PRC");
		ResponseDto<MedidaDto> respuestaEsperada = new ResponseDto<MedidaDto>();
		respuestaEsperada.setRespuesta(medidaDto);

		when(service.guardar(medidaDto)).thenReturn(respuestaEsperada);

		ResponseDto<MedidaDto> respuesta = this.service.guardar(medidaDto);

		assertThat(medidaDto.getAbreviatura()).isEqualTo(respuesta.getRespuesta().getAbreviatura());
	}
	
	@Test
	@Order(5)
	public void testEliminarRegistroPorId() {

		MedidaDto medidaDto = new MedidaDto();
		medidaDto.setId(2L);
		medidaDto.setBorrado(true);
		
		ResponseDto<MedidaDto> respuestaEsperada = new ResponseDto<MedidaDto>();
		respuestaEsperada.setRespuesta(medidaDto);

		when(service.eliminar(Mockito.anyLong())).thenReturn(respuestaEsperada);

		ResponseDto<MedidaDto> respuesta = this.service.eliminar(medidaDto.getId());

		assertThat(respuesta.getRespuesta().getBorrado()).isTrue();
	}
	
	public Stream<MedidaDto> getValues() {

		MedidaDto medidaDto = new MedidaDto();

		medidaDto.setId(1L);
		medidaDto.setNombre("PORCENTAJE");
		medidaDto.setAbreviatura("POR");

		return Stream.of(medidaDto);
	}
}

Anotaciones

@TestMethodOrder(OrderAnnotation.class)

@TestMethodOrder es una anotación de nivel de tipo, que se utiliza para configurar un método de ordenamiento para la ejecución de pruebas unitarias.

@Order(<numeración>)

El valor está ordenado por el método de prueba.

@TestInstance(Lifecycle.PER_CLASS)

La anotación @TestInstance nos permite configurar el ciclo de vida de las pruebas de JUnit 5. @TestInstance tiene dos: LifeCycle.PER_METHOD (el predeterminado) y LifeCycle.PER_CLASS. Este último, nos permite pedirle a JUnit que cree solo una instancia de la clase de prueba y la reutilice entre pruebas.

@BeforeEach

La anotación @BeforeEach se utiliza para señalar que el método anotado debe ejecutarse antes de cada @Test de la clase actual.

@ParameterizedTest

Las pruebas parametrizadas son como otras pruebas, excepto que agregamos la anotación @ParameterizedTest.

@MethodSource("<método>")
La anotación @MethodSource("<método>") proporciona acceso a los valores devueltos de la clase en la que se declara esta anotación.

mock(MedidaService.class)

El método mock() crea una copia de la implementación de MedidaService y el mecanismo utilizado es el proxy para la generación de la instancia.

Referencias

[1] Bolaños, D. (2008). Pruebas de software y JUnit: un análisis en profundidad y ejemplos prácticos. Pearson Prentice Hall.

[2] Gulati, S., & Sharma, R. (2017). Java Unit Testing with JUnit 5: Test Driven Development with JUnit 5. Apress.