CodeGym /Blog Java /Random-ES /Pruebas unitarias en Java con JUnit
Autor
Edward Izraitel
Software Engineer at Champions Oncology

Pruebas unitarias en Java con JUnit

Publicado en el grupo Random-ES

¿Qué son las pruebas unitarias en Java?

Antes de comenzar a aprender JUnit en Java, repasemos brevemente qué es la prueba unitaria y por qué es tan popular (si ya conoce estas cosas, vaya a "¿Cómo escribo una prueba JUnit en Java?"). Las pruebas unitarias en Java hacen que el desarrollo de software a gran escala sea mucho más eficiente y sencillo. Puede ayudar tanto a las personas como a los equipos a reducir innumerables horas de depuración y agilizar enormemente el proceso de colaboración. Pruebas unitarias en Java con JUnit - 1

https://junit.org/junit4/

La idea esencial de las pruebas unitarias es esta: escriba pruebas atómicas de características individuales (llamadas pruebas unitarias) y agregue lentamente más características después de probar y asegurarse de que las anteriores funcionen. Es una idea extremadamente simple pero poderosa. Como ejemplo de cómo se vería este proceso, imagina que estás construyendo una calculadora científica virtual. Además de los operadores aritméticos aparentes ( +, -, x, %), esta calculadora tendría características avanzadas que requieren otras subcaracterísticas para trabajar dentro de ella. Para calcular exponentes, su calculadora debe poder multiplicar correctamente. Entonces, un enfoque de prueba unitaria para construir y probar esta calculadora sería:
  • Escribe una función de suma. Pruébelo cuidadosamente, cámbielo, repita hasta que funcione.
  • Haz lo mismo para las funciones de resta, multiplicación y división.
  • Use estos operadores base para escribir funciones de operador más avanzadas como exponentes, luego pruebe esas funciones también.
Esto garantiza que las funciones que se basan en otras subfunciones más pequeñas no solo funcionen correctamente por sí mismas, sino que no tengan subfunciones defectuosas dentro de ellas. Por ejemplo, si estoy probando la función de exponente y algo sale mal, sé que el error probablemente no esté en la subfunción de multiplicación, porque la función de multiplicación ya se probó exhaustivamente. Esto elimina en gran medida la cantidad total de código que necesito rastrear e inspeccionar para encontrar el error. Con suerte, este ejemplo trivial deja en claro cómo se estructura el proceso de pensamiento en torno a las pruebas unitarias. Pero, ¿cómo interactúan las pruebas unitarias con el resto del proceso de desarrollo de software? ¿Qué sucede si tiene características aún más complejas, que deben poder trabajar y comunicarse juntas? Las pruebas unitarias son insuficientes para garantizar que características tan complejas puedan funcionar correctamente juntas. De hecho, es solo el primer paso de los Cuatro Niveles de Pruebas de Software (utilizo letras mayúsculas porque me refiero al estándar de la industria o al enfoque más común para probar software). Los últimos tres pasos sonPruebas de integración , pruebas de sistemas y pruebas de aceptación. Todo esto probablemente signifique exactamente lo que cree que significan, pero permítame aclararlo: la prueba de integración es lo que haríamos para garantizar que las "características complejas" mencionadas anteriormente interactúen correctamente entre sí. (p. ej., asegurarse de que la calculadora pueda manejar “3 + 7 * 4 - 2”) La prueba del sistema es probar el diseño general de un sistema en particular; a menudo hay múltiples sistemas de características complejas que trabajan juntas en un producto, por lo que los agrupa en sistemas y los prueba individualmente. (por ejemplo, si estuviera construyendo una calculadora gráfica, primero construiría el 'sistema' aritmético para manejar números, probando hasta que funcione según lo previsto, y luego construiría y probaría el 'sistema' gráfico para manejar retiros, como se basaría en el sistema aritmético). Las pruebas de aceptación son pruebas a nivel de usuario; es ver si todos los sistemas pueden funcionar en sincronía para crear un producto terminado listo para ser aceptado por los usuarios (por ejemplo, usuarios que prueban la calculadora). Los desarrolladores de software a veces pueden ignorar este paso final del proceso, ya que las empresas a menudo hacen que otros empleados implementen pruebas de usuario (beta) por separado.

¿Cómo escribo una prueba JUnit en Java?

Ahora que tiene una idea más clara de los beneficios y limitaciones de las pruebas unitarias, ¡echemos un vistazo a un poco de código! Usaremos un marco de prueba popular de Java llamado JUnit (otro popular es TestNG, que también puede usar si lo desea. Son muy similares, sintácticamente; TestNG está inspirado en JUnit). Puede descargar e instalar JUnit aquí . Para este código de ejemplo, continuaremos con el ejemplo de 'calculadora científica' que mencioné anteriormente; es bastante simple entenderlo, y el código de prueba es muy fácil. La práctica convencional es escribir clases de prueba separadas para cada una de sus clases, así que eso es lo que haremos. Supongamos que en este punto tenemos un Math.javaarchivo con todas las funciones matemáticas (incluyendo Math.add), y estamos escribiendo unMathTests.javaarchivo en el mismo paquete. Ahora configuremos las instrucciones de importación y el cuerpo de la clase: (POSIBLE PREGUNTA DE ENTREVISTA JUnit: Es posible que se le pregunte dónde colocar su prueba JUnit y si necesita o no importar sus archivos fuente. Si está escribiendo sus clases de prueba en el mismo paquete que sus clases principales, entonces no necesita ninguna declaración de importación para sus archivos de origen en la clase de prueba. De lo contrario, ¡asegúrese de que está importando sus archivos de origen!)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
La primera declaración de importación nos da el @Testencabezado. Escribimos ' @Test' directamente encima de cada definición de función de prueba, para que JUnit sepa que esta es una prueba unitaria singular que se puede ejecutar por separado. Más adelante, le mostraré cómo puede ejecutar pruebas unitarias específicas utilizando este encabezado. La segunda declaración de importación nos ahorra un poco de tipeo. La función principal de JUnit que usamos para probar nuestras funciones es to Assert.assertEquals(), que toma dos parámetros (valor real y valor esperado) y se asegura de que sean iguales. Tener esta segunda declaración de importación nos permite escribir ' assertEquals(...' en lugar de tener que especificar cada vez de qué paquete forma parte. ¡Ahora escribamos un caso de prueba muy simple para verificar que 2 + 2 es de hecho 4!

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
Repasemos cada una de las cinco líneas de la función de prueba y lo que hacen: Línea 5: este @Testencabezado especifica que la definición de función a continuación add_twoPlusTwo_returnsFour()es de hecho una función de prueba que JUnit puede ejecutar por separado. Línea 6: Esta es la firma de la función para nuestro caso de prueba. Los casos de prueba son siempre muy singulares; solo prueban un ejemplo específico, como 2+2=4. Es una convención nombrar sus casos de prueba en la forma “ [function]_[params]_returns[expected]()”, donde [function]es el nombre de la función que está probando, [params]son los parámetros de ejemplo específicos que está probando y [expected]es el valor de retorno esperado de la función. Las funciones de prueba casi siempre tienen un tipo de retorno de ' void' porque el punto principal de toda la función es ejecutarassertEquals, que mostrará en la consola si su prueba pasó o no; no necesita ningún otro dato para ser devuelto en cualquier lugar. Línea 7: Declaramos una finalvariable ' ' del tipo de retorno de Math.add (int), y la llamamos 'esperada' por convención. Su valor es la respuesta que estamos esperando (4). Línea 8: Declaramos una finalvariable ' ' del tipo de retorno de Math.add (int), y la llamamos 'real' por convención. Su valor es el resultado de Math.add(2, 2). Línea 9: La línea dorada. Esta es la línea que compara lo real y lo esperado y nos dice que pasamos la prueba solo si son iguales. El primer parámetro pasado "2+2 es 4" es una descripción de la función de prueba.

¿Qué sucede si mi función arroja una excepción?

Si su ejemplo de prueba específico arroja una excepción en lugar de afirmar que un valor real y esperado son iguales, entonces JUnit tiene una forma de aclarar esto en el @Testencabezado. Echemos un vistazo a un ejemplo a continuación. Suponiendo que tenemos una función en Math.javacalled Math.divide, queremos asegurarnos de que las entradas no se puedan dividir por 0. En su lugar, tratar de llamar Math.divide(a, 0)a cualquier valor 'a' debería arrojar una excepción ( ArithmeticException.class). Lo especificamos en el encabezado como tal:

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
Puede tener más de una excepción para expectedExceptions, solo asegúrese de usar corchetes y comas para enumerar sus clases de excepción, como tal:

expectedException = {FirstException.class, SecondException.class, … }

¿Cómo ejecuto mis pruebas JUnit en Java?

Cómo agregar JUnit a IntelliJ: https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea Puede ejecutar su proyecto como lo haría normalmente con las pruebas. Ejecutar todas las pruebas en una clase de prueba las ejecutará en orden alfabético. En JUnit 5, puede agregar una prioridad a las pruebas agregando una @Orderetiqueta. Un ejemplo:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
Aunque a_test()viene antes b_test()alfabéticamente y en el código, b_test()correrá antes a_test()aquí, porque 1 viene antes que 2 en Orden. Eso es todo sobre los conceptos básicos de JUnit. Ahora, abordemos un par de preguntas comunes de la entrevista de JUnit y aprendamos un poco más sobre JUnit en el camino.

Preguntas de la entrevista JUnit (información adicional)

Aquí he recopilado las preguntas de entrevista JUnit más populares. Si tiene algo que agregar, siéntase libre de hacerlo en los comentarios a continuación. P: ¿ A qué método puede llamar en su método de prueba para fallar automáticamente en una prueba? A: fail ("¡descripción del error aquí!"); P: Estás probando una clase Dog; para probar un objeto Dog, debe crear una instancia antes de poder ejecutar pruebas en él. Así que escribes una función setUp() para crear una instancia del Perro. Solo desea ejecutar esta función una vez durante todas las pruebas. ¿Qué debe poner directamente encima de la firma de la función setUp() para que JUnit sepa ejecutar setUp() antes de ejecutar las pruebas? R: @BeforeClass (@BeforeAll en JUnit 5) P:¿Cuál debe ser la firma de función de la función setUp() descrita anteriormente? A: vacío estático público. Cualquier función con @BeforeClass (@BeforeAll en JUnit 5) o @AfterClass (@AfterAll en JUnit 5) debe ser estática. P: Ha terminado de probar la clase Perro. Escribe la función void tearDown() que limpia los datos e imprime información en la consola después de cada prueba. Desea que esta función se ejecute después de cada prueba. ¿Qué debe poner directamente encima de la firma de la función tearDown() para que JUnit sepa ejecutar tearDown() después de ejecutar cada prueba? R: @After (@AfterEach en JUnit 5)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION