Skip to content

Instantly share code, notes, and snippets.

@javierav
Created August 17, 2020 07:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save javierav/831b8f80dd350d834b832f1c56dd48b9 to your computer and use it in GitHub Desktop.
Save javierav/831b8f80dd350d834b832f1c56dd48b9 to your computer and use it in GitHub Desktop.
Cómo ejecutar los tests en paralelo en el CI con Knapsack

Uno de los aspectos más importantes de un sistema de integración contínua es la velocidad con la que se ejecutan las pruebas automáticas, pues cuanto mayor sea ese tiempo de ejecución más se tardará en ejecutar el resto de acciones asociadas: detección de fallos, despliegues o mezcla de ramas.

Por otra parte, por todos es conocido que aquellos test que interactúan con un navegador web simulando las acciones del usuario son los tests que más tardan en ejecutarse, dándose casos en los que este proceso se demora más de una hora.

En esta breve guía vamos a indicar cómo instalar y configurar nuestro proyecto la gema Knapsack, que nos va a permitir ejecutar nuestros tests en paralelo sin mucho esfuerzo.

Esta gema se encarga de dividir la ejecución de los tests (trabaja a nivel de archivo *_spec.rb) en base a dos parámetros: el número de jobs que vamos a ejecutar y el número de job actual. Adicionalmente trabaja con un archivo knapsack_rspec_report.json que contiene la duración en segundos de cada uno de los archivos de tests (generado previamente como veremos a continuación), que ayuda al algoritmo de reparto a dividir la carga de una forma más eficiente. Con esos dos datos más el archivo de la duración, la gema selecciona los tests que va a ejecutar en el job actual, y esa selección es siempre la misma con esos parámetros de entrada. De esta forma cada job es autónomo a la hora de repartirse los tests que debe ejecutar y todos quedan repartidos entre todos los jobs de forma automática.

Instalación

La instalación de esta gema se realiza de la forma habitual. Editamos nuestro Gemfile:

group :test, :development do
  gem 'knapsack'
end

Después instalamos las dependencias:

bundle install

Modificamos el archivo Rakefile para que cargue las nuevas tareas de la gema:

Knapsack.load_tasks if defined?(Knapsack)

Y cargamos el adaptador de RSpec editando el archivo spec/spec_helper.rb:

require 'knapsack'

Knapsack::Adapters::RSpecAdapter.bind

Ejecución

La primera vez que ejecutamos Knapsack no sabe la duración de cada uno de nuestros archivos de tests, por lo que tendremos que ejecutarlo en modo reporte para que genere un archivo con la duración de cada archivo que usará posteriormente para dividir los tests entre los distintos nodos. Para generar ese archivo, ejecutaremos el siguiente comando:

KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec/features

Esto generará un archivo de nombre knapsack_rspec_report.json que deberemos commitear en nuestro repositorio.

Es bastante aconsejable que la ejecución se haga en una máquina lo más parecida posible a las cuales posteriormente se usarán para ejecutar las pruebas. Si se hace en local, puede ser que los tiempos no coincidan con los que posteriormente tenga en el servidor de CI y por tanto el reparto y la duración de los tests se vea afectado.

Las siguientes veces, como ya tenemos generado el archivo, ejecutaremos los tests con el siguiente comando:

bundle exec rake knapsack:rspec

Configuración del CI

Para configurar Gitlab CI para que ejecute los tests en paralelo, haremos uso de la directiva parallel. Esta directiva hace que nuestro job se ejecute N veces pero con las variables CI_NODE_INDEX y CI_NODE_TOTAL establecidas por Gitlab. La primera hace referencia al número de job (empezando por 0) y la segunda al total de jobs levantados (igual al valor de la directiva parallel). Estas dos variables de entorno son interpretadas automáticamente por Knapsack para calcular los archivos que debe ejecutar.

features:
  stage: testing
  variables:
    KNAPSACK_TEST_FILE_PATTERN: "spec/features/**{,/*/**}/*_spec.rb"
  parallel: 5
  script:
    - bundle exec rake knapsack:rspec

Reintentar los errores

En muchas ocasiones usamos gemas tipo RSpec-Rerun para que vuelva a ejecutar los tests que han fallado hasta 3 veces antes de devolver fallo. Al usar Knapsack ya no es posible usar estas gemas, pero podemos hacer uso de un script en Bash para reintentar la ejecución y simular el comportamiento de esas librerías:

bin/rerun

#!/usr/bin/env bash

run_command() {
  echo "Run command: $1"
  eval "$1"
}

# Ejecutamos los tests por primera vez
run_command "$1"

# Si se han ejecutado los tests bien, finalizamos el script
if [ $? -eq 0 ]; then
  exit 0
fi

# Si han fallado los tests pero no hay tests que re-ejecutar, salimos con fallo
if [ ! -s "spec/examples.txt" ]; then
  exit 1
fi

# Si algún test ha fallado, lo reintentamos hasta 3 veces
count=1
st=1 # por defecto el resultado sera error

while true; do
  export RERUN_COUNT="$count"

  # ejecutamos solo los tests que han fallado
  run_command "bundle exec rspec --only-failures"

  # capturamos la salida
  if [ $? -eq 0 ]; then
    st=0 # si se han ejecutado bien actualizamos la salida
  fi

  # nos salimos si llegamos a 3 iteraciones o los tests se han ejecutado bien
  if [ $count -eq 3 ] || [ $st -eq 0 ]; then
    break
  fi

  # aumentamos el contador
  count=$((count+1))
done

# nos salimos devolviendo un status
exit $st

De esta forma modificaremos el script en la configuración del Gitlab CI para que use el script:

features:
  stage: testing
  variables:
    KNAPSACK_TEST_FILE_PATTERN: "spec/features/**{,/*/**}/*_spec.rb"
  parallel: 5
  script:
    - bin/rerun "bundle exec rake knapsack:rspec"

Para que esto funcione es importante asegurarnos de que tenemos configurado el archivo de reintentos de RSpec:

RSpec.configure do |config|
  config.example_status_persistence_file_path = 'spec/examples.txt'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment