Gradle Configuration
Tags: java, gradle
Published: 2022-03-14 21:40:54

---
title: Gradle Configuration
published: true
date: 2022-03-14 00:00:00 UTC
tags: java,gradle
canonical_url: https://jorge.aguilera.soy/blog/2022/gradle-new-configuration.html
---

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

<dl>
<dt>TIP</dt>
<dd>
<p>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</p>
</dd>
</dl>

```
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