Cómo testear en Elasticsearch 5 sin morir en el intento

3
3567

En este tutorial desgranamos las claves para poder construir tests de integración en Elasticsearch 5 sin perder los nervios por el camino.

Índice de contenidos

1. Introducción

Los tests de integración dentro de Elasticsearch siempre han tenido su truquillo.

Con la actualización a la versión 5 de Elasticsearch, han cambiado algunas cosillas que pueden que nos den algún que otro dolor de cabeza para poder solucionarlas.

Con este tutorial pretendo evitar que paséis por ello dándoos las claves para que no paséis ni un minuto atascados resolviendo problemas del contexto de ejecución de Elasticsearch.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.3 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Sierra 10.12
  • Entorno de desarrollo: Eclipse Neon
  • Apache Maven 3.3.3

3. Creación del proyecto

Creamos un proyecto Maven, a través del arquetipo «quick-start-maven», y eliminamos todo aquello que le sobra (como es el fichero App.java y AppTest.java).

En este tutorial, únicamente nos vamos a centrar en cómo preparar la clase de tests de integración utilizando Elasticsearch 5, concretamente la versión 5.0.1.

proyecto-vacio-elasticsearch5-test-int

4. Importación de las dependencias necesarias

Basándonos en la documentación oficial de Elasticsearch v5, añadimos en el fichero ‘pom.xml’ las dependencias necesarias para poder construir y ejecutar tests de integración.

Adicionalmente, añadimos también la dependencia a la librería de Hamcrest para poder añadir algo más de legibilidad a nuestros tests.

	<dependencies>
			<dependency>
				<groupId>org.apache.lucene</groupId>
				<artifactId>lucene-test-framework</artifactId>
				<version>6.2.1</version>
				<scope>test</scope>
			</dependency>
			<dependency>
				<groupId>org.elasticsearch.test</groupId>
				<artifactId>framework</artifactId>
				<version>5.0.1</version>
				<scope>test</scope>
			</dependency>
			<dependency>
				<groupId>org.hamcrest</groupId>
				<artifactId>hamcrest-all</artifactId>
				<version>1.3</version>
			</dependency>
		</dependencies>

5. Construyendo el primer test

En teoría, ya podemos comenzar a construir nuestros tests de integración sin ningún problema.

Para evitar quebraderos de cabeza adicionales y como esto es un test de integración, esto es, que entran en juego varias piezas para ejecutar el tests, a mi me gusta empezar con un test vacío para que únicamente nos aseguremos de que el contexto de integración del test se levanta correctamente.

En este sentido, comenzamos construyendo una clase de test de integración vacía. En esta ocasión, yo he llamado a la clase ES5TestIT.

ES5TestIT.java
	package com.autentia.tutoriales.es5;

	public class ES5TestIT {

	}

Siguiendo las indicaciones de la documentación oficial de Elasticsearch, extendemos la clase de test de ‘ESIntegTestCase’. Con esto nos aseguramos de que una vez que ejecutemos el test, se levantará todo el contexto de Elasticsearch sin necesidad de tener un servidor externo corriendo.

	package com.autentia.tutoriales.es5;

	import org.elasticsearch.test.ESIntegTestCase;

	public class ES5TestIT extends ESIntegTestCase{

	}

Continuamos añadiendo nuestro primer test. En esta ocasión, vamos a construir un test vacío para asegurarnos únicamente de que todo el contexto de integración funciona correctamente.

	package com.autentia.tutoriales.es5;

	import static org.hamcrest.CoreMatchers.is;

	import org.elasticsearch.test.ESIntegTestCase;
	import org.junit.Test;

	public class ES5TestIT extends ESIntegTestCase{

		@Test
		public void shouldInitializeContextsSuccessfully() {
			assertThat(true, is(true));
		}
	
	}

Y finalmente, ejecutamos el test de integración. Este test, según la documentación oficial, debería levantar un Elasticsearch embebido y finalizarlo correctamente.

La realidad es, que antes de que esto llegue a pasar, hay que resolver algunos problemas.

6. Resolviendo la falta de dependencias

Si habéis seguido el tutorial y habéis llegado a este punto, el error que debería haberos salido es el siguiente:

error-dependencias-test-integration-elasticsearch5

Es decir, que para poder ejecutar el test de integración es necesario incluir un par de dependencias más relacionadas con el sistema de Logging.

Para resolver este error, añadimos las dependencias de log4j al fichero ‘pom.xml’, tanto el API como el Core.

	<dependencies>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>2.6.2</version>
		</dependency>

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.6.2</version>
		</dependency>

		<dependency>
			<groupId>org.apache.lucene</groupId>
			<artifactId>lucene-test-framework</artifactId>
			<version>6.2.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.elasticsearch.test</groupId>
			<artifactId>framework</artifactId>
			<version>5.0.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.hamcrest</groupId>
			<artifactId>hamcrest-all</artifactId>
			<version>1.3</version>
		</dependency>
	</dependencies>

Y volvemos a ejecutar el test de integración para ver si se han resuelto los errores de falta de dependencias, y poder levantar el contexto de forma correcta.

7. Con el JarHell hemos topado

Si habéis seguido los pasos del tutorial hasta aquí, y habéis ejecutado el test, es muy probable que os haya salido este mensaje de error.

error-jarhell-test-integracion-elasticsearch5

Y aquí está el famoso error de JarHell… infierno de librerías!! Este dichoso error sí que es un infierno.

La clase JarHell (vaya nombrecito) es una clase que forma parte del framework de testing de Elasticsearch, y lo que hace es recorrer cada una de las dependencias verificando que las clases que se disponen en las dependencias están una, y solo una vez.

Es alucinante descubrir cuantísimas librerías tienen exactamente copy&paste de clases de otras librerías: contenido idéntico, mismo qualified name (es decir, mismo paquete!), etc.

La investigación de este error me ha llevado por la consulta de decenas de webs, decompilación de librerías para comprobar con mis propios ojos lo que este error estaba indicando, probando cosas tan absurdas como empezar a meter exclusions en el pom.xml para ver si se resolvía (si claro que se resolvía, pero dejaba de funcionar otras cosas por que faltaban otras clases!!! Lógico).

Como sería la cosa, que en la versión 2.4 de Elasticsearch, disponían de un método de desactivación del JarHell incluyendo una propiedad a nivel de máquina virtual en la ejecución de los tests. Pero amigos, en la versión 5 de Elasticsearch, (al menos en la versión en la que se está redactando este tutorial que es la 5.0.1), ese método de desactivación ha desaparecido del código fuente!! Ya no podemos desactivarlo a nivel de propiedad de VM en la ejecución del test.

Como decía un famoso anuncio de TV: Una solución quiero!

8. Resolviendo el infierno del JarHell

Tras la revisión de unos cuantos hilos de discusión en la comunidad de Elasticsearch, uno de los miembros planteaba la solución de engañar al ClassLoader haciendo que cargase la clase de JarHell que nosotros queremos, en vez de cargar la del propio framework de testing de Elasticsearch.

Esta es la solución que vamos a implementar: Vamos a desarmar el infierno del JarHell sustituyéndola por una clase totalmente dummy. Puede que quizás esta solución nos esté ensuciando el código fuente, pero teniendo en cuenta cuales son las alternativas y que esto solo afecta las clases de testing, podemos asumir este cambio. Al menos hasta que se proporcione otras alternativas (en versiones posteriores?)

Para resolverlo, se crea la clase org.elasticsearch.bootstrap.JarHell dentro de src/test/java con el siguiente contenido:

	package org.elasticsearch.bootstrap;

		import java.io.IOException;
		import java.net.URISyntaxException;
		import java.net.URL;

		//IMPORTANT: Needed since the -Dtests.jarhell.check has been disabled in ES 5.0
		//DO NOT REMOVE
		public class JarHell {
		
			public static void checkJarHell() throws IOException, URISyntaxException {
			
				//IMPORTANT: Needed since the -Dtests.jarhell.check has been disabled in ES 5.0
				//DO NOT REMOVE
			}
		
			public static URL[] parseClassPath()  {
				return new URL[0];
			}
		}

Puede que cuando vayamos profundizando en la generación de nuestros tests de integración, haya que ir añadiendo nuevos métodos dummy que hagan el Override de los métodos de la clase JarHell. Para el ejemplo que vamos a mostrar, es más que suficiente añadir los métodos que hemos expuesto anteriormente.

project-explorer-jarhell

Finalmente, volvemos a ejecutar el test de integración para comprobar si el contexto levanta correctamente finalmente.

9. Queda detenido por el SecurityManager

Si habéis seguido todos los pasos del tutorial hasta aquí, tras la ejecución del test de integración habrá aparecido el siguiente error:

security-manager-error-elasticsearch5-testit

Pues parece ser que esta vez, el SecurityManager nos ha detenido ya que el test de integración no tiene política de seguridad declarada ni aceptada para poder ejecutar el test completamente.

Parece ser que la ejecución de este test de integración requiere unos permisos de seguridad especiales que se ven reflejadas a través de políticas seguridad. Pero ni con esas.

Tras mucho buscar, y mucho probar a meter ficheros «.policy» dentro de todos los directorios habidos y por haber, indicándole políticas garantizando la propiedad que se indica en los errores, y un sinfín de pruebas, el test de integración no es capaz de encontrar esas políticas de seguridad.

¿Cómo lo hemos resuelto os preguntaréis? Seguid leyendo.

10. Resolviendo problemas con el SecurityManager

En realidad no es una resolución del problema, sino más bien, una desactivación del problema. La solución pasa por indicar que no queremos que el SecurityManager realice un chequeo de los privilegios de aquellas operaciones que lo requieran.

Para ello, es necesario añadir un argumento a las opciones de la VM cada vez que se vaya a ejecutar un nuevo test: -Dtests.security.manager=false

solve-security-manager-es5-testit

Este es el aspecto del Runner de Eclipse al haberle añadido la nueva propiedad.

Finalmente, ejecutamos el test de integración para ver si con esto, ya hemos resuelto todos los problemas.

11. Contexto levantado! Lo conseguimos

Tras la realización de estos pasos, y tras haber ejecutado el test de integración parece ser que por fin se ha levantado el contexto de forma correcta, y no se ha quejado de ningún otro problema.

es5-testit-success

A partir de aquí, podemos empezar a montar la lógica del test propiamente dicha, pero eso ya será objeto de un nuevo tutorial. Podemos utilizar esta base para poder disponer nuestros tests de integración en Elasticsearch v5.0.1

12. Conclusiones

A continuación os dejo las conclusiones de la construcción de test de integración en Elasticsearch 5.0.1:

  • Es necesario añadir las dependencias de log4j para la ejecución de los tests.
  • Engañamos al entorno de ejecución cargando una clase JarHell que no es la de Elasticsearch para poder continuar con la ejecución del test
  • Desactivamos la inspección de privilegios del SecurityManager para poder continuar con la ejecución de los tests.

13. Contacto

¿Habéis tenido algún que otro problema con los tests de integración de Elasticsearch v5? Déjame un comentario en este artículo o coméntamelo a través de mi cuenta de Twitter: @drodriguezhdez.

Echa un vistazo al código del tutorial en mi repositorio de GitHub: Source code del Tutorial.

14. Referencias

3 COMENTARIOS

  1. Hola Daniel,

    Estas son de esas batallas que, o te encuentras un tutorial como éste en el que alguien se haya pegado con ellas, o lo más probable, es que acabes rindiéndote en alguno de los pasos que tú has resuelto con tanto acierto.

    Enhorabuena por el tutorial.

  2. […] En este tutorial veremos cómo desarrollar pruebas cuando construimos componentes que interaccionan con Elasticsearch gracias al componente de testing que nos ofrecen desde el equipo de Elastic […]

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad