Junit Conditional Test Execution

Junit: Conditional Test Execution

La API de extensión ExecutionCondition en JUnit Jupiter permite a los desarrolladores habilitar o deshabilitar una clase de prueba o un método de prueba según ciertas condiciones de manera programática. El ejemplo más sencillo de tal condición es el DisabledCondition incorporado, que admite la anotación @Disabled (see Disabling Tests).

Además de @Disabled, JUnit Jupiter también admite varias otras condiciones basadas en anotaciones en el paquete org.junit.jupiter.api.condition que permiten a los desarrolladores habilitar o deshabilitar clases de prueba y métodos de prueba de manera declarativa. Si deseas proporcionar detalles sobre por qué podrían estar deshabilitados, cada anotación asociada con estas condiciones incorporadas tiene un atributo disabledReason disponible para ese propósito.

Cuando se registran varias extensiones de ExecutionCondition, una clase de prueba o un método de prueba se deshabilita tan pronto como una de las condiciones devuelve deshabilitada. Si una clase de prueba se deshabilita, todos los métodos de prueba dentro de esa clase también se deshabilitan automáticamente. Si un método de prueba se deshabilita, esto impide la ejecución del método de prueba y las devoluciones de llamada del ciclo de vida a nivel de método, como los métodos @BeforeEach, @AfterEach y las APIs de extensión correspondientes. Sin embargo, esto no impide que la clase de prueba sea instanciada, ni impide la ejecución de las devoluciones de llamada del ciclo de vida a nivel de clase, como los métodos @BeforeAll, @AfterAll y las APIs de extensión correspondientes.

See ExecutionCondition and the following sections for details.

Composed Annotations

Ten en cuenta que cualquiera de las anotaciones condicionales listadas en las siguientes secciones también puede usarse como una meta-anotación para crear una anotación compuesta personalizada. Por ejemplo, la anotación @TestOnMac en la demostración de @EnabledOnOs muestra cómo puedes combinar @Test y @EnabledOnOs en una única anotación reutilizable.

Conditional annotations in JUnit Jupiter are not @Inherited. Consequently, if you wish to apply the same semantics to subclasses, each conditional annotation must be redeclared on each subclass.


⚠️ Salvo que se indique lo contrario, cada una de las anotaciones condicionales listadas en las siguientes secciones solo puede ser declarada una vez en una interfaz de prueba, clase de prueba o método de prueba determinado. Si una anotación condicional está presente de forma directa, indirecta o meta-presente varias veces en un elemento determinado, solo se usará la primera anotación que JUnit descubra; cualquier declaración adicional será ignorada de manera silenciosa. Sin embargo, ten en cuenta que cada anotación condicional puede usarse en conjunto con otras anotaciones condicionales en el paquete org.junit.jupiter.api.condition.

Operating System and Architecture Conditions

Un contenedor o prueba puede ser habilitado o deshabilitado en un sistema operativo particular, arquitectura, o una combinación de ambos mediante las anotaciones @EnabledOnOs y @DisabledOnOs.

Conditional execution based on operating system

@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
    // ...
}

@TestOnMac
void testOnMac() {
    // ...
}

@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
    // ...
}

@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
    // ...
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}

Conditional execution based on architecture

@Test
@EnabledOnOs(architectures = "aarch64")
void onAarch64() {
    // ...
}

@Test
@DisabledOnOs(architectures = "x86_64")
void notOnX86_64() {
    // ...
}

@Test
@EnabledOnOs(value = MAC, architectures = "aarch64")
void onNewMacs() {
    // ...
}

@Test
@DisabledOnOs(value = MAC, architectures = "aarch64")
void notOnNewMacs() {
    // ...
}

Java Runtime Environment Conditions

Un contenedor o prueba puede habilitarse o deshabilitarse en versiones particulares del Entorno de Ejecución de Java (JRE) mediante las anotaciones @EnabledOnJre y @DisabledOnJre, o en un rango particular de versiones del JRE mediante las anotaciones @EnabledForJreRange y @DisabledForJreRange. El rango, de forma predeterminada, utiliza JRE.JAVA_8 como límite inferior y JRE.OTHER como límite superior, lo que permite el uso de rangos semiabiertos.

El siguiente ejemplo demuestra el uso de estas anotaciones con constantes predefinidas del enum JRE.

@Test
@EnabledOnJre(JAVA_17)
void onlyOnJava17() {
    // ...
}

@Test
@EnabledOnJre({ JAVA_17, JAVA_21 })
void onJava17And21() {
    // ...
}

@Test
@EnabledForJreRange(min = JAVA_9, max = JAVA_11)
void fromJava9To11() {
    // ...
}

@Test
@EnabledForJreRange(min = JAVA_9)
void onJava9AndHigher() {
    // ...
}

@Test
@EnabledForJreRange(max = JAVA_11)
void fromJava8To11() {
    // ...
}

@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
    // ...
}

@Test
@DisabledForJreRange(min = JAVA_9, max = JAVA_11)
void notFromJava9To11() {
    // ...
}

@Test
@DisabledForJreRange(min = JAVA_9)
void notOnJava9AndHigher() {
    // ...
}

@Test
@DisabledForJreRange(max = JAVA_11)
void notFromJava8To11() {
    // ...
}

Dado que las constantes del enum JRE son estáticas para una versión determinada de JUnit, es posible que necesites configurar una versión de Java que no esté soportada por dicho enum. Por ejemplo, a partir de JUnit Jupiter 5.12, el enum JRE define JAVA_25 como la versión más alta de Java soportada. Sin embargo, podrías querer ejecutar tus pruebas en versiones posteriores de Java.

Para admitir este tipo de casos, puedes especificar versiones arbitrarias de Java mediante el atributo versions en @EnabledOnJre y @DisabledOnJre, así como mediante los atributos minVersion y maxVersion en @EnabledForJreRange y @DisabledForJreRange.

El siguiente ejemplo demuestra el uso de estas anotaciones con versiones arbitrarias de Java.

@Test
@EnabledOnJre(versions = 26)
void onlyOnJava26() {
    // ...
}

@Test
@EnabledOnJre(versions = { 25, 26 })
// Can also be expressed as follows.
// @EnabledOnJre(value = JAVA_25, versions = 26)
void onJava25And26() {
    // ...
}

@Test
@EnabledForJreRange(minVersion = 26)
void onJava26AndHigher() {
    // ...
}

@Test
@EnabledForJreRange(minVersion = 25, maxVersion = 27)
// Can also be expressed as follows.
// @EnabledForJreRange(min = JAVA_25, maxVersion = 27)
void fromJava25To27() {
    // ...
}

@Test
@DisabledOnJre(versions = 26)
void notOnJava26() {
    // ...
}

@Test
@DisabledOnJre(versions = { 25, 26 })
// Can also be expressed as follows.
// @DisabledOnJre(value = JAVA_25, versions = 26)
void notOnJava25And26() {
    // ...
}

@Test
@DisabledForJreRange(minVersion = 26)
void notOnJava26AndHigher() {
    // ...
}

@Test
@DisabledForJreRange(minVersion = 25, maxVersion = 27)
// Can also be expressed as follows.
// @DisabledForJreRange(min = JAVA_25, maxVersion = 27)
void notFromJava25To27() {
    // ...
}

Native Image Conditions

Un contenedor o prueba puede habilitarse o deshabilitarse dentro de una imagen nativa de GraalVM mediante las anotaciones @EnabledInNativeImage y @DisabledInNativeImage. Estas anotaciones se utilizan comúnmente al ejecutar pruebas dentro de una imagen nativa usando los complementos de Gradle y Maven del proyecto GraalVM Native Build Tools.

@Test
@EnabledInNativeImage
void onlyWithinNativeImage() {
    // ...
}

@Test
@DisabledInNativeImage
void neverWithinNativeImage() {
    // ...
}

System Property Conditions

Un contenedor o prueba puede habilitarse o deshabilitarse según el valor de una propiedad del sistema de la JVM mediante las anotaciones @EnabledIfSystemProperty y @DisabledIfSystemProperty. El valor proporcionado a través del atributo matches se interpretará como una expresión regular.

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
    // ...
}

@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
    // ...
}

❕A partir de JUnit Jupiter 5.6, las anotaciones @EnabledIfSystemProperty y @DisabledIfSystemProperty son anotaciones repetibles. En consecuencia, pueden declararse múltiples veces en una interfaz de prueba, clase de prueba o método de prueba. Específicamente, estas anotaciones serán detectadas si están presentes de forma directa, indirecta o como metaanotaciones en un determinado elemento.

Environment Variable Conditions

Un contenedor o prueba puede habilitarse o deshabilitarse según el valor de una variable de entorno del sistema operativo subyacente mediante las anotaciones @EnabledIfEnvironmentVariable y @DisabledIfEnvironmentVariable. El valor proporcionado a través del atributo matches se interpretará como una expresión regular.

@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
    // ...
}

@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
    // ...
}

❕A partir de JUnit Jupiter 5.6, las anotaciones @EnabledIfEnvironmentVariable y @DisabledIfEnvironmentVariable son anotaciones repetibles. En consecuencia, pueden declararse múltiples veces en una interfaz de prueba, clase de prueba o método de prueba. Específicamente, estas anotaciones serán detectadas si están presentes de forma directa, indirecta o como metaanotaciones en un determinado elemento.

Custom Conditions

Como alternativa a la implementación de una ExecutionCondition, un contenedor o prueba puede habilitarse o deshabilitarse en función de un método condicional configurado mediante las anotaciones @EnabledIf y @DisabledIf. Un método condicional debe tener un tipo de retorno booleano y puede no aceptar argumentos o aceptar un solo argumento de tipo ExtensionContext.

La siguiente clase de prueba demuestra cómo configurar un método local llamado customCondition mediante @EnabledIf y @DisabledIf.

@Test
@EnabledIf("customCondition")
void enabled() {
    // ...
}

@Test
@DisabledIf("customCondition")
void disabled() {
    // ...
}

boolean customCondition() {
    return true;
}

Alternativamente, el método condicional puede ubicarse fuera de la clase de prueba. En este caso, debe ser referenciado por su nombre completo (fully qualified name), como se demuestra en el siguiente ejemplo.

package example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;

class ExternalCustomConditionDemo {

    @Test
    @EnabledIf("example.ExternalCondition#customCondition")
    void enabled() {
        // ...
    }

}

class ExternalCondition {

    static boolean customCondition() {
        return true;
    }

}

❕ Hay varios casos en los que un método condicional debe ser estático:

  • Cuando @EnabledIf o @DisabledIf se utiliza a nivel de clase
  • Cuando @EnabledIf o @DisabledIf se utiliza en un método anotado con @ParameterizedTest o @TestTemplate
  • Cuando el método condicional se encuentra en una clase externa

En cualquier otro caso, puedes utilizar métodos condicionales ya sean estáticos o de instancia.

💡

A menudo, puedes utilizar un método estático existente en una clase de utilidad como una condición personalizada.

Por ejemplo, java.awt.GraphicsEnvironment proporciona un método public static boolean isHeadless() que se puede usar para determinar si el entorno actual no soporta una pantalla gráfica. Así, si tienes una prueba que depende del soporte gráfico, puedes deshabilitarla cuando dicho soporte no esté disponible de la siguiente manera.

@DisabledIf(value = "java.awt.GraphicsEnvironment#isHeadless",
    disabledReason = "headless environment")