Realizando tests de integración con Elasticsearch

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.

Índice de contenidos


1. Introducción

Como ya sabéis el testing es algo esencial en el desempeño día a día de nuestro trabajo como desarrolladores, ya hemos hablado en diversas ocasiones sobre su importancia.

Si bien ya existe algún tutorial sobre el testing de elasticsearch, como este de Daniel Rodríguez, en este tutorial veremos como desarrollar pruebas cuando construimos componentes que interaccionan con Elasticsearch gracias al componente de testing que no ofrecen desde el equipo de Elastic.

Este componente, utilizado internamente en Elastic para el desarrollo de Elasticsearch, permite configurar infraestructuras de cluster formadas por múltiples nodos facilitándonos el camino para la codificación de las pruebas que validen la existencia de errores en nuestro código.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 17′ (2.66 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Entorno de desarrollo: IntelliJ 2017.1
  • Apache Maven 3.3.9

3. Dependencias

Lo primero que tendremos que hacer para poder hacer uso de esta biblioteca es declarar las dependencias del mismo.

En este caso, procederemos sobre una aplicación gestionada mediante maven, con lo que definiremos dichas dependencias en el fichero pom.xml.

pom.xml

4. Tests de integración

4.1. Ejemplo básico de test

A continuación mostramos el código fuente de un test de integración muy sencillo que nos servirá a modo de ejemplo sobre los puntos que necesitamos cumplir para crear nuestros test de integración.

ExampleElasticSearchIT.java

Haciendo que nuestros tests hereden de ESIntegTestCase no tendremos que preocuparnos por montar la infraestructura necesaria para dar soporte a nuestros tests puesto que será la propia biblioteca la que se encargue de proveernos de todo lo necesario.

En el snipet de código anterior se muestra cómo crear un índice (createIndex(“test”);) y asegurarnos de que es correcto (ensureGreen();) a través de un cliente (client()) se dará de alta un nuevo documento dentro del índice con un mapping concreto:
client().prepareIndex("test", "type", "1").setSource("field", "xxx").execute().actionGet();

Forzaremos la disponibilidad de la información para las búsquedas (refresh();) y posteriormente realizamos una petición de búsqueda:
SearchResponse searchResponse =
client().prepareSearch("test").setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();

Para finalizar realizaremos las aserciones correspondientes que validen nuestro código.


4.2. Configuración

El grado de configuración de la infraestructura de pruebas dependerá de nosotros en todo momento, pudiendo establecer cualquier tipo de particularidad necesaria para nuestros tests.

La configuración “out-of-the-box” genera un cluster que dispondrá de Scope SUITE, entre 1 y 3 o 6 nodos de datos (dependiendo del tipo de ejecución), entre 1 y 3 nodos maestros y 1 nodo coordinador.

Los distintos Scope (SUITE o TEST) marcarán el ámbito para el cual se va a crear el cluster y se pueden configurar mediante la anotación @ClusterScope(scope=SUITE).

  • SUITE: Se creará un cluster nuevo por suite de tests, borrando todos los índices y templates entre cada test.
  • TEST: Se creará un cluster nuevo por cada test.

Esta anotación también nos permite configurar otros aspectos relativos al cluster como pueden ser: número de nodos de datos, número máximo y mínimo de nodos de datos (por si queremos utilizar un valor aleatorio acotado), si damos soporte a nodos master dedicados, número de nodos cliente…

Podemos utilizar otras anotaciones existentes dentro de la biblioteca a nivel de test para establecer otras configuraciones, como son:

  • @Nightly: Establece que el test se ejecuta solo en construcciones nigthly. Esta anotación tiene consecuencias a nivel de configuración del cluster .
  • @Backwards: Establece que se trata de un test de retrocompatibilidad.
  • @AwaitsFix: Establece que el test esta pendiente de resolver algún bug.
  • @BadApple: Establece que el test falla de manera random.

Si bien en el ejemplo del punto anterior hemos decidido que la biblioteca nos devuelva un cliente conectado a un nodo aleatorio (a través de client()), dentro InternalTestCluster disponemos de un amplio conjunto de métodos para recuperar clientes conectados a nodos dedistinta naturaleza dentro del cluster:

  • client()
  • coordOnlyNodeClient()
  • dataNodeClient()
  • masterClient()
  • nonMasterClient()
  • smartClient()
  • transportClient()

4.3. Helpers

La clase ESIntegTestCase también nos proveerá de una serie de metodos (helpers) que nos serán de gran utilidad en nuestros tests. El conjunto de helpers se puede categorizar de acuerdo a la funcionalidad que otorga pudiendo encontrar aserciones, métodos de configuración del cluster, métodos de explotación del cluster, etc. Si bien la documentación de estos helpers puede consultarse aquí, a continuación veremos en detalle algunos de ellos:

  • refresh(): dispara el proceso de refresco de los índices que existan en el cluster.
  • waitForDocs(numDocs): espera hasta que un número dado de documentos (numDocs) se encuentra visible para búsquedas.
  • ensureYellow() y ensureGreen(): comprueba el estado del cluster y si no es Amarillo o Verde, según el caso, lanza un error de tipo AssertionError.
  • waitNoPendingTasksOnAll(): espera hasta que no haya tareas pendientes en ningún nodo.
  • waitForRelocation(): espera hasta que todos los shards reubicados se encuentren activos.

Gracias a la jerarquía establecida, también dispondremos de los métodos helpers que exponen ESTestCase y LuceneTestCase.


4.4. Aserciones

Como en el caso de los Helpers, al extender la funcionalidad de ESIntgeTestCase tendremos acceso a una serie de asserts que pueden facilitarnos la generación de nuestros tests. Algunos ejemplos pueden ser:

  • assertAllShardsOnNodes(index, pattern): esta aserción comprueba que todos los shards se asignan a nodos con un patrón dado.
  • assertResultsAndLogOnFailure(expectedResults, searchResponse): comprueba que el número de documentos devueltop es el esperado, en caso contrario deja un log con la información.
  • assertPathHasBeenCleared(path): comprueba que el path especificado no contiene ningún fichero.

Como en el punto anterior, la jerarquía establecida nos añade todas las aserciones que se exponen tanto en ESTestCase y LuceneTestCase


5. Caso de uso

Para mostrar un caso no tan trivial como el del ejemplo en el punto 4.1.

BookSearchIT.java
BookSearchIT.java

6. Conclusiones

Como hemos visto, disponer de la infraestructura necesaria para realizar tests de integración de nuestros componentes de interacción de elasticsearch puede suponer una tarea ardua, sin embargo, gracias a la biblioteca de testing de Elastic, podemos lidiar con la mayoría de las situaciones de forma rápida y elegante.