Gradle Configuration

Seguro que ya lo sabes, pero por si acaso, Gradle es una herramienta para construir artefactos de software. Es una alternativa al archifamoso Maven que en lugar de basarse en una definición de las tareas en XML, com hace este, ha desarrollado su propio DSL siendo el fichero build.gradle el "punto de entrada" donde se definen las tareas, dependencias, etc.

En Gradle una configuration es un conjunto de artefactos junto con sus dependencias. Así por ejemplo, en un proyecto típico de Java, Gradle ofrece de por sí dos configuraciones, main y test, representadas cada una por sus respectivos directorios.

Así cuando ubicamos un fichero .java en el directorio src/main/java estamos asignandolo a la configuración main, por lo que se usarán las dependencias configuradas en main sobre él, mientras que si ubicamos el fichero en el directorio test se aplicarán otras dependencias.

Esto es útil para poder indicar de forma sencilla qué dependencias van en cada configuration. Por ejemplo, las dependencias anotadas con testImplementation no se utilizan en construir los artefactos ubicados en main

Lo bueno de Gradle es que podemos extender este modelo para incluir nuevas configuraciones de una forma sencilla.

Objetivo

Queremos incluir en nuestro proyecto un nuevo tipo de Tests con unas necesidades específicas y queremos que se integren dentro del resto de tareas pudiendo definir sus depencias, etc.

Por necesidades específicas puedes entender cualquier cosa: unos tests que sólo se pueden ejecutar en un entorno dockerizado concreto, con unas variables de entorno particulares, requieren un software instalado previamente, etc

La solución habitual es etiquetarlos con algún tipo de condición de tal forma que si no se cumplen los requisitos, el test se ignore, pero al final terminamos teniendo una mezcla de tests que dificulta su ejecución e incluso depuración.

En este ejemplo lo que vamos a crear es una configuración específica para testear una imagen nativa creada por el proyecto usando GraalVM. Estos tests van a ejecutar la aplicación nativa y comprobar su funcionamiento como si fueran un usuario ejecutandola desde consola

nativeCliTest

Como ya hemos dicho, vamos a ubicar los test en un nuevo directorio, a la misma altura que el directorio test (y main) . Una vez terminada la configuración, nuestro editor lo va a reconocer como uno más.

Diagram

Build.gradle

Para incluir este nuevo directorio como una nueva configuración en gradle, modificaremos el build.gradle

def nativeCliTest = sourceSets.create('nativeCliTest')

configurations[nativeCliTest.implementationConfigurationName].extendsFrom(configurations.testImplementation)
configurations[nativeCliTest.runtimeOnlyConfigurationName].extendsFrom(configurations.testRuntimeOnly)

Con estas líneas estamos creando una nueva configuración igual a la de test

dependencies{

    api '....'
    implementation '....'
    testImplementation '.....'

    nativeCliTestImplementation project
    nativeCliTestImplementation "org.codehaus.groovy:groovy-sql:3.0.9"
}

Le indicamos a gradle que los artefactos bajo la nueva configuración requieren de las dependencias del proyecto.

Si para estos tipos de test requerimos alguna dependencia extra podemos indicarla como en el ejemplo (groovy-sql) y así no estaremos "contaminando" a las otras configuraciones (es decir, en este ejemplo los artefactos de main ni de test no ven esta nueva dependencia)

def nativeCliTestTask = tasks.register('nativeCliTest', Test) {
    description = 'Runs nativeCli tests.'
    group = 'verification'
    useJUnitPlatform()

    testClassesDirs = nativeCliTest.output.classesDirs
    classpath = configurations[nativeCliTest.runtimeClasspathConfigurationName] + nativeCliTest.output

    dependsOn nativeCompile
    environment 'NATIVE_BINARY_PATH', "$buildDir/native/nativeCompile/miaplicacion"
}

tasks.named('check') {
    dependsOn(nativeCliTestTask)
}

Creamos una nueva tarea nativeCliTestTask (o como quieras llamarla), que extiende Test y la configuramos para que use las dependencias de nativeCliTest

Así mismo la incluimos dentro de las tareas necesarias para completar la tarea check, es decir, cuando le indiquemos a Gradle que queremos ejecutar la tarea check, gradle ejecutará :

  • compileXXXX

  • build

  • test

  • integrationTest (si lo hubiera)

  • nativeCompile

  • nativeCliTestTask

  • check

Test

Con esta configuración cualquier test que creemos en la carpeta nativeCliTest dispondrá de una variable de entorno NATIVE_BINARY_PATH con la ruta a la imagen nativa generada previamente

TIP

Obviamente este es mi caso particular. En el tuyo lo que hacen particulares a estos test puede ser cualquier otra variable de entorno, ejecutable, etc

import groovy.sql.Sql
import org.testcontainers.containers.MySQLContainer
import spock.lang.Specification
import spock.lang.Timeout

class MysqlTest extends Specification {

    static MySQLContainer container

    static {
        container = new MySQLContainer("mysql:5.6")
        container.start()
    }

    @Timeout(30)
    def 'should run native binary' () {
        given:
        def BIN = System.getenv('NATIVE_BINARY_PATH')
        def CLI = [BIN,
                '-u', container.username,
                '-p', container.password,
                '--url', container.jdbcUrl,
                '--driver', 'com.mysql.cj.jdbc.Driver',
                '--dialect', 'mysql'
                ]

        when:
        def proc = new ProcessBuilder()
                .command(CLI)
                .redirectErrorStream(true)
                .start()
        and:
        def result = proc.waitFor()

        then:
        result == 0

        and:
        Sql.newInstance(container.jdbcUrl, container.username, container.password)
                .rows("SELECT * FROM mytable")
    }
}

IDE

(Al menos con Intellij) Al configurar un nuevo sourceSet el IDE es capaz de reconecer qué tipo es y qué dependencias necesita, etc y nos permite ejecutar y depurar cada test de forma independiente si lo desamos

Conclusion

En este post me he centrado en añadir un nuevo tipo de test al estilo de integrationTest o functionalTest pero puedes extenderlo a cualquier otro tipo de artefactos que requieran unas dependencias especiales

Follow comments at Telegram group Or subscribe to the Channel Telegram channel

2019 - 2022 | Mixed with Bootstrap | Baked with JBake v2.6.7