Domine las pruebas unitarias de Java: sumérjase en herramientas, mejores prácticas y técnicas para garantizar un código sólido. ¡Aumente la confiabilidad del software y entregue sin problemas!
¿Quiere impulsar sus esfuerzos de desarrollo de Java? Esta guía explora el mundo de las pruebas de Java y cubre conceptos básicos y técnicas avanzadas. Aprenderá sobre la importancia del desarrollo basado en pruebas (TDD), la configuración y el uso de JUnit 5, las afirmaciones para validar el comportamiento y las mejores prácticas para escribir pruebas de alta calidad. Si es un principiante que busca comprender los conceptos básicos o un experto que busca mejorar sus habilidades, encontrará información valiosa sobre las pruebas de Java.
¿Qué son las pruebas unitarias de Java?
El propósito de las pruebas unitarias es aislar "unidades" de código y probarlas para garantizar que funcionen como se espera. Una "unidad" es la parte comprobable más pequeña de una aplicación, normalmente un único método o clase. De esta manera, cuando una prueba falla, es fácil identificar qué pieza o “unidad” no funciona como se esperaba.
Pero antes de profundizar en los pasos específicos involucrados en las pruebas unitarias, veamos por qué deberíamos crear pruebas unitarias.
¿Por qué escribir pruebas unitarias?
Los desarrolladores de Java a menudo tienen que probar el código manualmente para ver si funciona como se esperaba. Escribir pruebas unitarias ayuda a automatizar este proceso y garantiza que las mismas pruebas se ejecuten en el mismo entorno bajo las mismas condiciones iniciales.
Las pruebas unitarias tienen una serie de ventajas, que incluyen:
- Solución de problemas sencilla: las pruebas JUnit revelarán cuando su código no funcione como se esperaba. Esto hace que sea más fácil identificar errores o problemas importantes antes de que se intensifiquen y se infiltren en sus versiones de producción.
- Habilite la refactorización de código: las pruebas unitarias proporcionan una red de seguridad cuando su código cambia, por lo que puede refactorizarlo y modificarlo con la confianza de que no introducirá nuevos errores en su software.
- Mejore la calidad del código: las pruebas unitarias alientan a los desarrolladores a escribir código más modular, comprobable y mantenible.
Aunque escribir pruebas unitarias puede llevar mucho tiempo al principio, en última instancia puede reducir el tiempo general de desarrollo al reducir el esfuerzo dedicado a corregir errores y reelaborar el código más adelante en el proceso de desarrollo.
Desarrollo basado en pruebas
Test Driven Development es una práctica de desarrollo de software en la que los desarrolladores escriben métodos de prueba antes de escribir código. La idea es evaluar primero el comportamiento previsto. Esto, en muchos casos, facilita la implementación del comportamiento real. También es más difícil introducir errores. Puede corregir cualquier error que haya aparecido escribiendo pruebas adicionales que expongan el comportamiento defectuoso del código.
El proceso TDD normalmente implica tres pasos:
- Escriba pruebas fallidas: describa el comportamiento previsto de su aplicación y escriba casos de prueba basados en eso. Se espera que las pruebas fracasen.
- Escribir código: el siguiente paso es escribir un código para que las pruebas pasen. El código está escrito sólo para cumplir con los requisitos de la prueba y nada más.
- Reestructuración: busque formas de mejorar el código manteniendo su funcionalidad. Esto puede incluir simplificar el código, eliminar duplicaciones o mejorar su rendimiento.
Instalación de la Unidad 5
Ahora que hemos cubierto la importancia y el proceso del desarrollo basado en pruebas, podemos explorar cómo configurar JUnit 5, uno de los marcos de prueba de Java más populares.
experto
Para instalar JUnit 5 en Maven, agregue las siguientes dependencias en el archivo pom.xml.
< dependencias > < dependencia > < ID de grupo >org.junit.jupiter</ ID de grupo > < artefactoId >junit-jupiter-api</ artefactoId > < versión >5.9.2</ versión > < ámbito >prueba</ ámbito > </ dependencia ><!-- Para ejecutar pruebas parametrizadas --> < dependencia > < ID de grupo >org.junit.jupiter</ ID de grupo > < artefactoId >junit-jupiter-params</ artefactoId > < versión >5.9.2</ versión > < ámbito >prueba</ ámbito > </ dependencia > < dependencias >
Gradle
Para instalar y configurar JUnit 5 en Gradle, agregue las siguientes líneas a su archivo build.gradle.
prueba {useJUnitPlatform} dependencias { implementación de prueba 'org.junit.jupiter:junit-jupiter-api:5.9.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' }
Paquetes JUnit
Tanto org.junit como org.junit.jupiter.api son paquetes de pruebas unitarias de Java que brindan soporte para escribir y ejecutar pruebas.
Pero org.junit es el marco de prueba más antiguo introducido con JUnit 4, mientras que org.junit.jupiter.api es el marco de prueba de software Java más nuevo introducido con JUnit 5. Este último se basa en JUnit 4 y agrega nuevas características y funcionalidades. El marco JUnit 5 admite pruebas parametrizadas, pruebas paralelas y lambdas, entre otras características. Para nuestros propósitos, utilizaremos JUnit 5.
Cómo escribir pruebas unitarias
Podemos marcar un método como prueba agregando la anotación @Test. El método marcado para la prueba debe ser público.
En JUnit 5, hay dos formas de utilizar métodos de aserción como afirmarEquals, afirmarTrue, etc.: importación estática e importación regular.
Una importación estática le permite utilizar sólo miembros estáticos (como métodos) de una clase sin especificar el nombre de la clase. En JUnit, las importaciones estáticas se usan comúnmente para métodos de aserción. Por ejemplo, en lugar de escribir Assert.assertEquals(esperado, real), puede usar afirmarEquals(esperado, real) directamente después de usar una declaración de importación estática.
importar estática org.junit.jupiter.api.Assert.*; prueba principal de clase pública { @Prueba público vacío dosPlusTwoEqualsFalse { resultado entero = 2 + 2; afirmarEquals(4, resultado); } }
Afirmaciones
JUnit 5 proporciona varios métodos de aserción integrados que se pueden utilizar para verificar el comportamiento del código bajo prueba. Una afirmación es simplemente un método que compara el resultado de una unidad de prueba con un resultado esperado.
A lo largo de este artículo, veremos varios métodos de prueba. Teniendo en cuenta las ideas del desarrollo basado en pruebas, no analizaremos la implementación del código en ninguno de estos casos. En su lugar, discutiremos el comportamiento previsto y los casos extremos (si los hay) y escribiremos pruebas JUnit basadas en eso.
Assert.assertEquals y Assert.assertNotEquals
El método afirmarEquals se utiliza para comprobar si dos valores son iguales o no. La prueba pasa si el valor esperado es igual al valor real.
En el ejemplo, estamos probando un método de "suma", que toma dos números enteros y devuelve su suma.
@Prueba anular tresMásCincoEqualsOcho { Calculadora calculadora = nueva Calculadora; // sintaxis: afirmarEquals (valor esperado, valor real, mensaje); afirmarEquals(8, calculadora.add(3, 5)); }
Al comparar objetos, el método afirmarEquals utiliza el método "equals" del objeto para determinar si son iguales. Si el método "igual" no se anula, sólo entonces realizará una comparación de referencia. Por ejemplo, llamar a afirmarEquals en dos cadenas llamará al método string.equals(string).
Tenga esto en cuenta porque las matrices no reemplazan el método "igual". Llamar a array1.equals(array2) solo comparará sus referencias. Por lo tanto, no debe utilizar afirmarEquals para comparar matrices o cualquier objeto que no anule el método igual. Si desea comparar matrices, use Arrays.equals(array1, array2), y si desea probar la igualdad de las matrices, use el método afirmarArrayEquals.
Assert.assetIgual
Este método compara las referencias de dos objetos o valores. La prueba pasa cuando los dos objetos tienen las mismas referencias. De lo contrario, fracasará.
Assert.assertTrue y Assert.assertFalse
El método afirmarTrue comprueba si una condición determinada es verdadera o no. La prueba sólo pasará si la condición es verdadera. Aquí estamos probando el método mod, que devuelve el módulo de un número.
@Prueba vacío debeGetPositiveNumber { // sintaxis: afirmarVerdadero(condición) afirmarVerdadero(calculadora.mod(-32) >= 0) }
Asimismo, el método afirmarFalse pasa la prueba sólo cuando la condición es falsa.
Assert.assertNull y Assert.assertNonNull
Como habrás adivinado, el método afirmarNull espera un valor nulo. De manera similar, el método afirmarNonNull espera cualquier valor que no sea nulo.
Assert.assertArrayEquals
Anteriormente, mencionamos que el uso de afirmarEquals en matrices no produce el resultado deseado. Si desea comparar dos matrices elemento por elemento, utilice afirmarArrayEquals.
Juegos de prueba
Los dispositivos de prueba JUnit son un conjunto de objetos, datos o código que se utilizan para preparar un entorno de prueba y proporcionar un punto de partida conocido para las pruebas. Esto incluye las tareas de preparación y limpieza necesarias para probar una unidad de código específica.
Antes de cada y después de cada
La anotación @BeforeEach en JUnit se usa para marcar un método que debe ejecutarse antes de cada prueba en una clase de prueba. La anotación @BeforeEach se utiliza para preparar el entorno de prueba o configurar los recursos necesarios antes de ejecutar cada caso de prueba.
En los ejemplos anteriores, en lugar de crear una instancia del objeto Calculadora dentro de cada método de prueba, podemos crear una instancia de él en un método separado que se llamará antes de que el ejecutor de la prueba ejecute una prueba.
importar org.junit.jupiter.api.BeforeEach; importar org.junit.jupiter.api.Test; class CalculatorTest { Calculadora calculadora; @Antes deCada configuración anulada { calculadora = nueva Calculadora; } @Prueba anular dosMásTwoEqualsFour { afirmarEquals(4, calculadora.add(2, 2)); } }
La anotación @AfterEach en JUnit se usa para marcar un método que debe ejecutarse después de cada prueba en una clase de prueba. La anotación @AfterEach se puede utilizar para limpiar cualquier recurso (como bases de datos o conexiones de red) o restablecer estados que se crearon durante la ejecución del caso de prueba.
Antes de todo y después de todo.
Las anotaciones @BeforeAll y @AfterAll en JUnit se utilizan para marcar los métodos que deben ejecutarse una vez antes y después de todos los casos de prueba ejecutados.
El caso de uso principal del método @BeforeAll es configurar cualquier recurso global o inicializar cualquier estado compartido que deba estar disponible para todos los casos de prueba de la clase. Por ejemplo, si una clase de prueba requiere una conexión de base de datos, el método @BeforeAll se puede utilizar para crear una única conexión de base de datos que puedan compartir todos los casos de prueba.
Algunas afirmaciones más
Después de cubrir anotaciones como @AfterEach, @BeforeAll y @AfterAll, ahora podemos profundizar en algunas afirmaciones avanzadas en JUnit, comenzando con técnicas para probar excepciones.
Excepciones de prueba
Para verificar si un fragmento de código generará una excepción o no, puede usar afirmarThrows, que toma la referencia de clase de la excepción esperada como primer argumento y el fragmento de código que desea probar como segundo argumento.
Ahora, digamos que queremos probar el método de “división” de nuestra clase Calculadora, que toma dos números enteros, divide el primer número entre el segundo y devuelve un valor doble. Sin embargo, genera una excepción (ArithmeticException) si el segundo argumento es cero. Podemos probar esto usando el método afirmarThrows.
@Prueba división de prueba nula { afirmarThrows(RuntimeException.class, -> calculadora.divide(32, 0)); }
Si ejecuta la prueba anterior, notará que la prueba pasó. Como se mencionó anteriormente, el método de división devolverá una ArithmeticException, pero no lo estamos verificando. El código anterior funciona porque afirmarThrows simplemente verifica si se lanzará una excepción, independientemente del tipo.
Utilice afirmarThrowsExactly para esperar un error de tipo fijo. En este caso, es mejor utilizar afirmarThrowsExactly.
@Prueba división de prueba nula { afirmarThrowsExactly(ArithmeticException.clase, -> calculadora.divide(32, 0)); }
El método afirmarNotThrows toma el código ejecutable como argumento y prueba si el código genera alguna excepción. La prueba pasa si no se lanzan excepciones.
Tiempos de espera de prueba
El método afirmarTimeout le permite probar si un bloque de código se completa dentro de un límite de tiempo específico. Aquí está la sintaxis de afirmarTimeout:
afirmarTimeout (tiempo de espera de duración, ejecutable ejecutable)
afirmarTimeout ejecuta el código bajo prueba en un hilo separado, y si el código se completa dentro del límite de tiempo especificado, la afirmación pasa. Si el código tarda más que el límite de tiempo especificado en completarse, la aserción falla y la prueba se marca como fallida.
@Prueba anular pruebaSlowOperation { afirmarTimeout(Duración.deSeconds(1), -> { Hilo.dormir(500); // simula una operación lenta }); }
La afirmación afirmarTimeoutPreemptively es un método que le permite probar si un bloque de código se completa dentro de un período de tiempo específico, al igual que afirmarTimeout. La única diferencia es que afirmarTimeoutPreemptive detiene la ejecución cuando se excede el límite de tiempo.
Pruebas dinámicas
Las pruebas dinámicas en JUnit son pruebas generadas en tiempo de ejecución en lugar de estar predefinidas. Permiten a los desarrolladores generar pruebas mediante programación basadas en datos de entrada. Las pruebas dinámicas se implementan utilizando la anotación @TestFactory. El método anotado @TestFactory debe devolver una secuencia, una colección o un iterador del tipo genérico DynamicTest.
Aquí estamos probando un método de resta que devuelve la diferencia entre el primer argumento y el segundo.
@TestFactory Stream<DynamicTest> pruebaSustraccion { Lista<Entero> números = Lista.de(3, 7, 14, 93); devolver números.stream .map((número) -> DynamicTest.dynamicTest("Prueba: " + número, -> afirmarEquals(número - 4, calculadora.restar(número, 4)); )); }
Pruebas parametrizadas
Las pruebas parametrizadas le permiten escribir un único método de prueba y ejecutarlo varias veces con diferentes argumentos. Esto puede resultar útil cuando desee probar un método con diferentes valores de entrada o combinaciones de valores.
Para crear una prueba parametrizada en JUnit 5, puede usar la anotación @ParameterizedTest y proporcionar argumentos usando anotaciones como @ValueSource, @CsvSource, @MethodSource, @ArgumentsSources, etc.
Pasar un argumento
La anotación @ValueSource toma una serie de valores únicos de cualquier tipo. En el siguiente ejemplo, estamos probando una función que verifica si un número determinado es impar o no. Aquí utilizamos anotaciones @ValueSource para obtener una lista de argumentos. El corredor de pruebas ejecuta la prueba para cada valor dado.
@PruebaParametrizada @ValueSource(ints = {3, 9, 77, 191}) anular testIfNumbersAreOdd ( int número) { afirmarTrue(calculadora.isOdd(número), "Verificar: " + número); }
Pasar múltiples argumentos
La anotación @CsvSource toma como entrada una lista de argumentos separados por comas, donde cada línea representa un conjunto de entradas al método de prueba. Aquí, en el siguiente ejemplo, estamos probando un método de multiplicación que devuelve el producto de dos números enteros.
@PruebaParametrizada @CsvSource({"3,4", "4,14", "15,-2"}) prueba vacíaMultiplicación ( int valor1, int valor2) { afirmarEquals(valor1 * valor2, calculadora.multiplicar(valor1, valor2)); }
Pasar valores nulos y vacíos
La anotación @NullSource proporciona un único argumento nulo. El método de prueba se ejecuta una vez con un argumento nulo.
La anotación @EmptySource proporciona un argumento vacío. Para cadenas, esta anotación proporcionará una cadena vacía como argumento.
Además, si desea utilizar argumentos nulos y vacíos, utilice la anotación @NullAndEmptySource.
Pasar enumeraciones
Cuando la anotación @EnumSource se usa con un método de prueba parametrizado, el método se ejecuta una vez para cada constante de enumeración especificada.
En el siguiente ejemplo, el ejecutor de pruebas ejecuta el método testWithEnum para cada valor de enumeración.
enumeración Color { ROJO VERDE AZUL } @PruebaParametrizada @EnumSource(Color.clase) void testWithEnum (Color color) { afirmarNotNull(color); }
De forma predeterminada, @EnumSource incluye todas las constantes definidas en el tipo de enumeración especificado. También puede personalizar la lista de constantes especificando uno o más de los siguientes atributos.
El atributo de nombre se utiliza para especificar los nombres de las constantes que se incluirán o excluirán, y el atributo de modo se utiliza para especificar si los nombres se incluirán o excluirán.
enumeración ColorEnum { ROJO VERDE AZUL } @PruebaParametrizada @EnumSource(valor = ColorEnum.class, nombres = {"ROJO", "VERDE"}, modo = EnumSource.Mode.EXCLUDE) anular testingEnums (ColorEnum colorEnum) { afirmarNotNull(colorEnum); }
En el ejemplo anterior, el caso de prueba se ejecutará solo una vez (para ColorEnum.BLUE).
Pasar argumentos de archivo
En el siguiente ejemplo, @CsvFileSource se utiliza para especificar un archivo CSV (test-data.csv) como argumento de origen para el método testWithCsvFileSource. El archivo CSV contiene tres columnas, que corresponden a los tres parámetros del método.
// Contenido del archivo .csv // src/test/resources/test-data.csv // 10, 2, 12 // 14, 3, 17 // 5, 3, 8 @PruebaParametrizada @CsvFileSource(recursos = "/test-data.csv") void testWithCsvFileSource (cadena de entrada1, cadena de entrada2, cadena esperada) { int iInput1 = Integer.parseInt(entrada1); int iInput2 = Integer.parseInt(entrada2); int iExpected = Integer.parseInt(esperado); afirmarEquals(iExpected, calculadora.add(iInput1, iInput2)); }
El atributo de recursos especifica la ruta al archivo CSV relativa al directorio src/test/resources en su proyecto. También puede utilizar una ruta absoluta si es necesario.
Tenga en cuenta que los valores del archivo CSV siempre se tratan como cadenas. Es posible que deba convertirlos en los tipos apropiados en su método de prueba.
Pasar valores de un método
La anotación @MethodSource se utiliza para especificar un método como fuente de argumentos para un método de prueba parametrizado. Esto puede resultar útil cuando desee generar casos de prueba basados en un algoritmo o estructura de datos personalizados.
En el siguiente ejemplo, estamos probando el método isPalindrome que toma un número entero como entrada y verifica si el número entero es un palíndromo o no.
Corriente estática <Argumentos> generarCasosdeprueba { devolver flujo.de( Argumentos.de(101, verdadero ), Argumentos.de(27, falso ), Argumentos.de(34143, verdadero ), Argumentos.de(40, falso ) ); } @PruebaParametrizada @MethodSource("generarCasosdePrueba") void testWithMethodSource ( int entrada, booleano esperado) { // el método isPalindrome(int number) comprueba si lo dado // la entrada es palíndromo o no afirmarEquals(esperado, calculadora.isPalindrome(entrada)); }
Argumentos personalizados
@ArgumentsSource (que no debe confundirse con ArgumentsSources) es una anotación que se puede utilizar para especificar un proveedor de argumentos personalizado para un método de prueba parametrizado. El proveedor de anotaciones personalizadas es una clase que proporciona argumentos al método de prueba. La clase debe implementar la interfaz ArgumentsProvider y anular su método provideArguments.
Considere el siguiente ejemplo:
clase estática StringArgumentsProvider implementa ArgumentsProvider { Frutas en cadena = {"manzana", "mango", "naranja"}; @Anular Transmisión pública <? extiende Argumentos> proporcionarArgumentos (ExtensionContext extensiónContext) lanza Excepción { return Stream.of(frutas).map(Argumentos::de); } } @PruebaParametrizada @ArgumentsSource(StringArgumentsProvider.clase) void testWithCustomArgumentsProvider (fruta de cadena) { afirmarNotNull(fruta); }
En este ejemplo, StringArgumentsProvider es un proveedor de argumentos personalizado que proporciona cadenas como argumentos de prueba. El proveedor implementa la interfaz ArgumentsProvider y anula su método provideArguments para devolver un flujo de argumentos.
Puede utilizar la anotación @ArgumentsSources para especificar múltiples fuentes de argumentos para un único método de prueba parametrizado.
Pruebas anidadas
En JUnit 5, las clases de prueba anidadas son una forma de agrupar pruebas relacionadas y organizarlas en una estructura jerárquica. Cada clase de prueba anidada puede contener su propia configuración, desmontaje y pruebas.
Para definir una clase de prueba anidada, use la anotación @Nested antes de una clase interna. El interior no debe ser estático.
clase Prueba de ejemplo { @Antes deCada configuración anulada1 {} @Prueba prueba nula1 {} @Anidado clase Prueba Anidada { @Antes deCada configuración nula2 {} @Prueba prueba nula2 { } @Prueba prueba nula3 { } } }
El código se ejecutará en el siguiente orden.
configuración1 -> prueba1 -> configuración1 -> configuración2 -> prueba2 -> configuración1 -> configuración2 -> prueba3
Así como una clase de prueba puede contener clases de prueba anidadas, una clase de prueba anidada también puede contener sus propias clases de prueba anidadas. Esto le permite crear una estructura jerárquica para sus pruebas, lo que facilita la organización y el mantenimiento de su código de prueba.
Banco de pruebas
JUnit Test Suites son una forma de organizar sus pruebas. Aunque las pruebas anidadas son una excelente manera de organizar las pruebas, a medida que aumenta la complejidad de un proyecto, se vuelven cada vez más difíciles de mantener. Además, antes de ejecutar cualquier método de prueba anidado, se ejecutan primero todos los dispositivos de prueba, lo que puede resultar innecesario. Por lo tanto, utilizamos conjuntos de pruebas para organizar nuestras pruebas con regularidad.
Para utilizar los conjuntos de pruebas JUnit, primero cree una nueva clase (por ejemplo, EjemploTestSuite). Luego agregue la anotación @RunWith(Suite.class) para indicarle al ejecutor de pruebas de Junit que use Suite.class para ejecutar las pruebas. El corredor Suite.class en JUnit le permite ejecutar múltiples clases de prueba como un conjunto de pruebas completo. Luego especifique las clases que desea ejecutar usando la anotación @SuiteClasses.
importar org.junit.runner.RunWith; importar org.junit.runners.Suite; importar org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.clase) @SuiteClasses({ CalculadoraPrueba.clase, CalculatorUtilsTest.clase }) clase pública CalculatorTestSuite {}
Mejores prácticas para redactar mejores pruebas
Ahora que hemos explorado afirmaciones específicas, debemos abordar las mejores prácticas que pueden maximizar la efectividad de las pruebas. Una pauta clave es mantener las pruebas simples y enfocadas, pero existen consideraciones adicionales. Profundicemos en algunos principios clave a seguir al escribir pruebas unitarias sólidas y eficientes.
- Escriba pruebas simples y enfocadas: las pruebas unitarias deben ser simples y enfocarse en probar un aspecto del código a la vez. Debe ser fácil de entender y mantener y proporcionar información clara sobre lo que se está probando.
- Utilice nombres de prueba descriptivos: los nombres de las pruebas deben ser descriptivos y proporcionar información clara sobre lo que se está probando. Esto ayuda a que el conjunto de pruebas sea más legible y comprensible. Para nombrar una prueba, utilice la anotación @DisplayName.
@Prueba @DisplayName("Comprobar nueve más siete es igual a dieciséis") anular dosMásTwoEqualsFour { afirmarEquals(16, calculadora.add(9,7)); }
- Uso de valores aleatorios en tiempo de ejecución: no se recomienda generar valores aleatorios en tiempo de ejecución para pruebas unitarias. El uso de valores aleatorios puede ayudar a garantizar que el código que pruebe sea sólido y pueda manejar una amplia gama de entradas. Los valores aleatorios pueden ayudar a revelar casos extremos y otros escenarios que pueden no ser evidentes en un caso de prueba estático. Sin embargo, el uso de valores aleatorios también puede hacer que las pruebas sean menos confiables y repetibles. Si se ejecuta la misma prueba varias veces, es posible que produzca resultados diferentes cada vez, lo que puede dificultar el diagnóstico y la corrección de problemas. Si se utilizan valores aleatorios, es importante documentar la semilla utilizada para generarlos para que las pruebas puedan reproducirse.
- Nunca pruebe los detalles de la implementación: las pruebas unitarias deben centrarse en probar el comportamiento de una unidad o componente, no en cómo se implementa. Los detalles de implementación de las pruebas pueden hacer que las pruebas sean frágiles y difíciles de mantener.
- Casos extremos: Los casos extremos son casos en los que su código puede fallar. Por ejemplo, si se trata de objetos, un caso extremo común es cuando el objeto es nulo. Asegúrese de cubrir todos los casos extremos al escribir pruebas.
- Patrón Organizar-Actuar-Afirmar (AAA): El patrón AAA es un patrón útil para estructurar pruebas. En este patrón, la fase Organizar configura los datos y el contexto de la prueba, la fase Actuar realiza la operación que se está probando y la fase Afirmar verifica que se obtuvieron los resultados esperados.
Mockito
Mockito es un marco de simulación de Java de código abierto que le permite crear y utilizar objetos simulados en pruebas unitarias. Los objetos simulados se utilizan para simular objetos reales en el sistema que son difíciles de probar de forma aislada.
Instalación
Para agregar Mockito a su proyecto, agregue la siguiente dependencia en pom.xml.
<!-- --> < dependencia > < ID de grupo >org.mockito</ ID de grupo > < artefactoId >mockito-core</ artefactoId > < versión >5.3.0</ versión > < ámbito >prueba</ ámbito > </ dependencia >
Si está utilizando Gradle, agregue lo siguiente a su build.gradle.
repositorios { mavenCentral } dependencias { testImplementation "org.mockito:mockito-core:3.+" }
Usando objetos simulados
En las pruebas unitarias, queremos probar el comportamiento de una unidad de código independientemente del resto del sistema. Sin embargo, a veces un módulo de código depende de otros módulos o de algunas dependencias externas que son difíciles o imposibles de probar de forma aislada. En este caso, utilizamos objetos simulados para simular el comportamiento de estas dependencias y aislar el módulo bajo prueba.
En el siguiente ejemplo, tenemos una clase de Usuario que queremos probar. La clase Usuario depende de una clase UserService responsable de recuperar datos de una base de datos. La clase UserService tiene un método llamado getUserById que busca información sobre un usuario en una base de datos y la devuelve.
Usuario de clase pública { identificación interna final privada ; Servicio de usuario final privado Servicio de usuario; Usuario público ( int id, UserService servicio de usuario) { este .userService = userService; este .id = identificación; } cadena pública getName { Información de usuario = userService.getUserById(id); devolver info.getName; } } Servicio de usuario de clase pública { Información de usuario pública getUserById ( int id) { // recuperar información del usuario de una base de datos } }
Para realizar una prueba unitaria del método getName de la clase Usuario, debemos probarlo de forma aislada de la clase UserService y la base de datos.
Una forma de hacerlo es utilizar un objeto simulado para simular el comportamiento de la clase UserService. Aquí hay un ejemplo de cómo hacer esto usando Mockito:
importar org.junit.jupiter.api.Test; importar org.mockito.Mockito; @Prueba prueba de anulación públicaGetName { UserService userService = Mockito.mock(UserService.class); Información de información de usuario = nueva Información de usuario (123, "Juan"); Mockito.when(userService.getUserById(123)).thenReturn(entidad); Usuario usuario = nuevo Usuario (123, servicio de usuario); Nombre de cadena = usuario.getName; afirmarEquals("Juan", nombre); Mockito.verify(userService).getUserById(123); }
En el ejemplo anterior, estamos creando un objeto simulado para la clase UserService usando el método Mockito.mock. Luego estamos definiendo el comportamiento del objeto simulado usando el método Mockito.when, que especifica que cuando se llama al método getUserById con el argumento 123, el objeto simulado debe devolver un objeto UserEntity con el nombre “John. "
Luego creamos un objeto Usuario con el UserService simulado y probamos el método getName. Finalmente, verificamos que el objeto simulado se usó correctamente usando el método Mockito.verify, que verifica que el método getUserById fue llamado con el argumento 123.
Usar un objeto simulado de esta manera nos permite probar el comportamiento del método getName de forma aislada de la clase UserService y la base de datos, asegurando que cualquier error o falla solo esté relacionado con el comportamiento de la propia clase Usuario.
Marcos de prueba de Java
JUnit es, con diferencia, la opción más popular cuando se trata de marcos de prueba. Sin embargo, existen muchas otras opciones. Éstos son algunos de ellos:
- TestNG: TestNG es otro marco de prueba de Java popular que admite una amplia variedad de escenarios de prueba, incluidas pruebas unitarias, pruebas funcionales y pruebas de integración. Proporciona funciones avanzadas como pruebas paralelas, pruebas de dependencias y pruebas basadas en datos.
- AssertJ: AssertJ es una biblioteca de aserciones de Java que proporciona una API fluida para definir aserciones. Proporciona una amplia variedad de afirmaciones para probar diferentes tipos de objetos y admite afirmaciones personalizadas.
- Hamcrest: Hamcrest es una biblioteca de aserciones de Java que proporciona una amplia variedad de comparadores para probar diferentes tipos de objetos. Permite a los desarrolladores escribir pruebas más expresivas y legibles utilizando afirmaciones en lenguaje natural.
- Selenium: Selenium es un marco de prueba de Java para probar aplicaciones web. Permite a los desarrolladores escribir pruebas automatizadas para aplicaciones web utilizando una variedad de lenguajes de programación, incluido Java.
- Cucumber: Cucumber es un marco de pruebas de Java que permite a los desarrolladores escribir pruebas automatizadas en un estilo de desarrollo basado en el comportamiento (BDD). Proporciona una sintaxis de lenguaje simple y natural para definir pruebas, lo que facilita la redacción de pruebas que sean fáciles de leer y comprender.
Conclusión
En este artículo, cubrimos todo lo que necesita saber para iniciar las pruebas unitarias utilizando JUnit y Mockito. También analizamos los principios del desarrollo basado en pruebas y por qué debería seguirlo.
Al adoptar un enfoque de desarrollo basado en pruebas, puede asegurarse de que su código se comporte según lo previsto. Pero como cualquier práctica de desarrollo de software, TDD tiene sus pros y sus contras, y su eficacia dependerá del proyecto y del equipo específicos. Para proyectos más grandes, contratar servicios de desarrollo de Java puede proporcionar experiencia en pruebas para implementar TDD correctamente según sus necesidades.
En última instancia, la decisión de utilizar TDD debe tener en cuenta los objetivos del proyecto, las habilidades del equipo y si los recursos externos de prueba de Java podrían ser beneficiosos. Con la comprensión adecuada de las ventajas y desventajas de TDD, incluso los equipos sin experiencia pueden aprovechar los beneficios de calidad de una metodología que prioriza las pruebas.
Si le gustó esto, asegúrese de consultar algunos de nuestros otros artículos sobre Java.
- Los 8 mejores IDE de Java y editores de texto
- Los 6 mejores marcos de GUI de Java
- Las 7 mejores bibliotecas de aprendizaje automático de Java
- Las 5 mejores herramientas de compilación de Java comparadas
- Listado de las 9 mejores herramientas de análisis de código estático de Java
- Concurrencia de Java: domine el arte del subproceso múltiple
Preguntas frecuentes
¿Cómo se manejan las dependencias al configurar pruebas unitarias en Java?
Las dependencias se gestionan automáticamente mediante herramientas de compilación como Maven y Gradle. Por ello, es muy recomendable su uso.
¿Puedes usar JUnit para probar código que no sea Java como JavaScript o Python?
No, no puedes usar JUnit para probar código que no sea Java. Sin embargo, lenguajes como Javascript y Python tienen sus propios marcos para pruebas unitarias. Por ejemplo, Javascript (ReacT) tiene Jest y Python tiene PyTest para pruebas unitarias.
¿Cuáles son algunos errores comunes que se deben evitar al escribir pruebas unitarias y cómo se pueden mitigar?
Al escribir pruebas unitarias, asegúrese de que sean simples y se centren en probar un aspecto del código a la vez. Utilice nombres descriptivos y agrupe pruebas similares. Intente cubrir todos los casos extremos.
Fuente: BairesDev