Tengo un proyecto que utiliza mucha reflexión, también sobre "nuevas" funciones de Java, como registros y clases selladas. Estoy escribiendo una clase como esta:
public class RecordHelper { public static boolean isRecord(Class<?> type) { return type.isRecord(); } }Por supuesto, esto solo funciona en Java 16 y superior, por lo que estoy tratando de configurar un archivo JAR de varias versiones, con una implementación predeterminada como esta:
public class RecordHelper { public static boolean isRecord(Class<?> type) { return false; } }Pude configurar eso con Maven usando esta publicación en Baeldung , y funciona muy bien. Es bueno, porque no se basa en perfiles separados para varias versiones, lo que mantiene limpio mi archivo pom.
Pero ahora necesito escribir pruebas.
Quiero poder ejecutar el conjunto de pruebas en todas las plataformas que quiero admitir, lo que significa todo, desde JDK 8 en adelante. (No me importa compilar en un JDK diferente antes de ejecutar las pruebas). Por supuesto, en JDK 16 y posteriores, también quiero probar cosas relacionadas con registros (y en 17 y posteriores, clases selladas), eso significa que tengo que compilar algunos registros, lo que significa que inevitablemente tendré algunos archivos de clase que no funcionarán en JDK anteriores.
En mi opinión, también tendría sentido tener algo así como un archivo JAR de versiones múltiples para las pruebas, donde las pruebas de registros se colocan en el lugar apropiado en META-INF/versions , pero, por supuesto, las pruebas generalmente no se empaquetan en un JAR, entonces eso no funciona.
¿Hay alguna manera de hacer que esto funcione en un proyecto Maven de un solo módulo sin demasiada repetición?
Por supuesto, tendría que 'acumular' clases de prueba a medida que sube la versión de JDK, por ejemplo, en JDK 8 solo tengo las clases de prueba 'regulares', en JDK 16 tengo las regulares y las de java16 , y en JDK 17 Tengo los regulares, los java16 y los java17 . Todavía no he encontrado una manera de expresar este tipo de cosas en Maven de manera concisa, pero no soy un experto en Maven.
¿O estoy mirando en la dirección equivocada y es preferible hacer un proyecto Maven de varios módulos, con el código principal en un módulo y las pruebas en otro, y luego generar un contenedor de varios módulos para las pruebas también? Si es así, ¿cómo ejecutaría este archivo jar en diferentes JDK?
Terminé creando una compilación de varios módulos, como sugiere @khmarbaise en uno de los comentarios debajo de la pregunta. También proporciona un ejemplo .
Con módulos llamados core , m16 , m17 , etc. Puedo colocar la mayor parte de las pruebas en el módulo core y las pruebas específicas de JDK en los módulos correspondientes. Incluso puedo probar la integración del código específico de JDK con el resto del módulo core , porque aparentemente en esta configuración, Maven elige la implementación de una clase en m17 sobre la implementación de la misma clase en el core , simulando así lo que sucede en un verdadero archivo jar de múltiples versiones. No estoy 100% seguro de si esto es una programación por coincidencia o no, pero funciona 😅
Otra ventaja de un enfoque de varios módulos es que si utilizo nuevas funciones de lenguaje en los submódulos específicos de JDK, mi IDE simplemente lo entenderá. Con el enfoque en el artículo de Baeldung que cité en la pregunta , eso no funciona y veo garabatos en todas partes.
Para probar la compilación en diferentes JDK, utilizo perfiles que se activan en la versión de JDK y que seleccionan los submódulos apropiados, como este:
<profile> <id>modules-jdk8</id> <activation> <jdk>[1.8,11)</jdk> </activation> <modules> <module>core</module> </modules> </profile> <profile> <id>modules-jdk16</id> <activation> <jdk>[16,17)</jdk> </activation> <modules> <module>core</module> <module>m16</module> </modules> </profile> <profile> <id>modules-jdk17</id> <activation> <jdk>[17,)</jdk> </activation> <modules> <module>core</module> <module>m16</module> <module>m17</module> <module>release</module> </modules> </profile>(Tal vez hubiera sido mejor un enfoque que usara cadenas de herramientas).
Tenga en cuenta que también debe haber un módulo de release , que tenga dependencias en todos los demás módulos, y que use el complemento Maven Assembly para crear realmente el archivo jar, y que coloque las clases específicas de JDK en los directorios apropiados en el jar.
He agregado una <maven.install>true</maven.install> a todos los módulos excepto al módulo de release , de modo que solo el archivo jar de versiones múltiples real se instalará en mi repositorio local. Esto es importante, porque también quiero publicar en Maven Central y no quiero ensuciarlo con todo tipo de archivos jar de medio producto.
Para probar un MRJAR, las clases deben empaquetarse como un jar, así que no utilices el método surefire con target/classes , sino que utilices la función failsafe durante la fase de verify . Y debe ejecutarlo al menos dos veces, una vez por versión de Java objetivo. Escribiría una prueba unitaria, que funciona para todas las versiones de Java, pero podría omitir ciertas pruebas.
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.Test; class RecordHelperTest { @Test void isNotARecord() { assertFalse( RecordHelper.isRecord(Object.class)); } @Test void isARecord() throws Exception { assumeTrue( Integer.parseInt( System.getProperty( "java.specification.version" ) ) >= 16 ); Class c = Class.forName( "jdk.net.UnixDomainPrincipal" ); assertTrue( RecordHelper.isRecord(c)); } }Cómo ejecutarlo dos veces, eso depende de usted, por ejemplo, configure el complemento a prueba de fallas dos veces con una cadena de herramientas diferente, o confíe en un servidor CI, que crea el proyecto con ambos JDK.
https://maven.apache.org/plugins/maven-compiler-plugin/multirelease.html describe varias opciones con sus ventajas y desventajas, ya que no existe una solución única para todos.
Se debe preferir una solución de varios módulos de Maven como la de @jqno y @khmarbaise a esta solución. El complemento Maven Invoker debe usarse solo cuando una configuración de varios módulos no es una opción.
El complemento Maven Invoker podría usarse para este propósito.
Siguiendo el diseño de directorio estándar sugerido por el complemento y asumiendo las versiones 8, 11 y 16 como objetivos de JDK, las pruebas específicas de JDK deben estructurarse en directorios separados:
src/it/jdk-8 para pruebas dirigidas a JDK 8.src/it/jdk-11 para pruebas dirigidas a JDK 11.src/it/jdk-16 para pruebas dirigidas a JDK 16. Cada grupo debe tener su propio POM donde se especifica la versión de Java en la configuración maven-compiler-plugin , por ejemplo, mediante el uso de la propiedad maven.compiler.release .
Cada grupo de prueba puede ejecutarse en varias versiones de Java sin duplicar las clases de prueba. Por ejemplo, para ejecutar pruebas de JDK 11 en ejecuciones de Java 11 y 16:
maven-jar-plugininvoker.goals = install para el grupo específico de prueba)dependenciesToScan .ventajas
src/it .Contras
src/it no son compatibles con IDE, por ejemplo, los archivos src/it/jdk-*/pom.xml no se detectan como proyectos Maven y los subdirectorios src/it/jdk-*/src/test no se detectan como prueba raíz de las fuentes.Un ejemplo completo está disponible en https://github.com/scordio/invoker-plugin-example .