A pesar de que todos saben que es importante y necesario probar su software, y que muchos lo hacen automáticamente, no se ha encontrado ninguna receta en los rincones de Habra sobre cómo configurar la combinación de productos tan populares en este nicho como (nuestro favorito) GitLab y JUnit. ¡Vamos a llenar este vacío!

Introducción
Para empezar, definiré el contexto:
- Dado que todas nuestras aplicaciones funcionan en Kubernetes, se considerará la ejecución de pruebas en la infraestructura correspondiente.
- Para la construcción y despliegue utilizamos (en el sentido de componentes de infraestructura, esto también significa automáticamente que se utiliza Helm).
- No entraré en detalles sobre cómo crear las pruebas: en nuestro caso, el cliente escribe las pruebas por sí mismo, y nosotros solo aseguramos su ejecución (y la presencia del informe correspondiente en la solicitud de fusión).
¿Cómo se verá la secuencia general de acciones?
- Construcción de la aplicación: omitiremos la descripción de esta etapa.
- Despliegue de la aplicación en un namespace separado del clúster de Kubernetes y ejecución de pruebas.
- Búsqueda de artefactos y análisis del informe JUnit por parte de GitLab.
- Eliminación del namespace creado previamente.
¡Ahora, a la implementación!
Configuración
GitLab CI
Comencemos con el fragmento .gitlab-ci.yaml, que describe el despliegue de la aplicación y la ejecución de pruebas. La lista resultó ser bastante extensa, por lo que se ha complementado considerablemente con comentarios:
variables:
# declaramos la versión de werf que vamos a utilizar
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# creamos el namespace en K8s, si no existe
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# descargamos werf y desplegamos — más detalles en la documentación
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#deploy-stage)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf deploy --stages-storage :local
--namespace ${CI_ENVIRONMENT_SLUG}
--set "global.commit_ref_slug=${CI_COMMIT_REF_SLUG:-''}"
# pasamos la variable `run_tests`
# se utilizará en el renderizado del lanzamiento Helm
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# cambiamos el timeout (puede haber pruebas largas) y lo pasamos al lanzamiento
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# creamos un directorio para el futuro informe, basado en $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# un workaround necesario, ya que GitLab quiere recibir los artefactos en su directorio de build
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# al finalizar las pruebas, eliminamos el lanzamiento junto con el Job
# (y, posiblemente, su infraestructura)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf dismiss --namespace ${CI_ENVIRONMENT_SLUG} --with-namespace
# permitimos fallos, pero puedes hacer lo contrario
allow_failure: true
variables:
RUN_TESTS: 'yes'
# configuramos el contexto en werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# utilizamos un runner con la etiqueta `werf-runner`
- werf-runner
artifacts:
# se requiere recoger un artefacto para que se pueda ver
# en el pipeline y descargarlo — por ejemplo, para un estudio más detenidos
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# los artefactos de más de una semana serán eliminados
expire_in: 7 day
# importante: estas líneas son responsables de la comprensión del informe por GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# para simplificar aquí se muestran solo dos etapas
# en realidad, tendrás más — al menos debido al despliegue
stages:
- build
- tests
build:
stage: build
script:
# construcción — de vuelta a la documentación de werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#build-stage)
- type multiwerf && source <(multiwerf use ${WERF_VERSION})
- werf version
- type werf && source <(werf ci-env gitlab --tagging-strategy tag-or-branch --verbose)
- werf build-and-publish --stages-storage :local
tags:
- werf-runner
except:
- schedules
run tests:
<<: *test-base
environment:
# "el alma" de la denominación del namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Ahora en el directorio .helm/templates crearemos un YAML con un Job — tests-job.yaml — para ejecutar pruebas y los recursos necesarios de Kubernetes. Para más explicaciones, véase después del listado:
{{- if eq .Values.global.run_tests "yes" }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: tests-script
data:
tests.sh: |
echo "======================"
echo "${APP_NAME} TESTS"
echo "======================"
cd \/app
npm run test:ci
cp report.xml \/app\/test_results\/${CI_COMMIT_REF_SLUG}\/\
echo ""
echo ""
echo ""
chown -R 999:999 \/app\/test_results\/${CI_COMMIT_REF_SLUG}
---
apiVersion: batch\/v1
kind: Job
metadata:
name: {{ .Chart.Name }}-test
annotations:
"helm.sh\/hook": post-install,post-upgrade
"helm.sh\/hook-weight": "2"
"werf\/watch-logs": "true"
spec:
activeDeadlineSeconds: {{ .Values.global.ci_timeout }}
backoffLimit: 1
template:
metadata:
name: {{ .Chart.Name }}-test
spec:
containers:
- name: test
command: ['bash', '-c', '\/app\/tests.sh']
{{ tuple "application" . | include "werf_container_image" | indent 8 }}
env:
- name: env
value: {{ .Values.global.env }}
- name: CI_COMMIT_REF_SLUG
value: {{ .Values.global.commit_ref_slug }}
- name: APP_NAME
value: {{ .Chart.Name }}
{{ tuple "application" . | include "werf_container_env" | indent 8 }}
volumeMounts:
- mountPath: \/app\/test_results\/\
name: data
- mountPath: \/app\/tests.sh
name: tests-script
subPath: tests.sh
tolerations:
- key: dedicated
operator: Exists
- key: node-role.kubernetes.io\/master
operator: Exists
restartPolicy: OnFailure
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ .Chart.Name }}-pvc
- name: tests-script
configMap:
name: tests-script
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Chart.Name }}-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mi
storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
volumeName: {{ .Values.global.commit_ref_slug }}
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: {{ .Values.global.commit_ref_slug }}
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Mi
local:
path: \/mnt\/tests\/\
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io\/hostname
operator: In
values:
- kube-master
persistentVolumeReclaimPolicy: Delete
storageClassName: {{ .Chart.Name }}-{{ .Values.global.commit_ref_slug }}
{{- end }} ¿Qué recursos están descritos en esta configuración? Al desplegar, creamos un namespace único para el proyecto (esto está indicado aún en .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) y lo desplegamos en:
- ConfigMap con el script de la prueba;
- Job con la descripción del pod y la directiva indicada
command, que es la que ejecuta las pruebas; - PV y PVC, que permiten almacenar los datos de las pruebas.
Tenga en cuenta la condición inicial con if al principio del manifiesto — por lo tanto, otros archivos YAML del Helm chart con la aplicación deben envolverse en la inversa estructura, para que no se despleguen durante las pruebas. Es decir:
{{- if ne .Values.global.run_tests "yes" }}
---
yo otro yaml
{{- end }}Sin embargo, si las pruebas requieren cierta infraestructura (por ejemplo, Redis, RabbitMQ, Mongo, PostgreSQL…) — sus YAML se pueden no desactivar. Expándalos también en un entorno de prueba… claro, ajustándolos a tu criterio.
Toque final
Como el ensamblaje y el despliegue mediante werf aún funcionan solo en el servidor de construcción (con gitlab-runner), y el pod con las pruebas se inicia en el maestro, será necesario crear un directorio /mnt/tests en el maestro y cederlo al runner, por ejemplo, a través de NFS. Un ejemplo desplegado con explicaciones se puede encontrar en .
El resultado será:
user@kube-master:~$ cat /etc/exports | grep tests
/mnt/tests IP_gitlab-builder/32(rw,nohide,insecure,no_subtree_check,sync,all_squash,anonuid=999,anongid=998)
user@gitlab-runner:~$ cat /etc/fstab | grep tests
IP_kube-master:/mnt/tests /mnt/tests nfs4 _netdev,auto 0 0Nadie impide crear un recurso compartido NFS directamente en gitlab-runner, tras lo cual se puede montar en los pods.
Nota
Quizás te preguntes, ¿por qué complicar las cosas creando un Job, si se puede simplemente ejecutar un script con las pruebas directamente en el shell-runner? La respuesta es bastante trivial…
Algunas pruebas requieren acceder a la infraestructura (MongoDB, RabbitMQ, PostgreSQL, etc.) para verificar la correcta funcionalidad con ellas. Estamos haciendo que la prueba sea unificada; con este enfoque, incluir entidades adicionales se vuelve sencillo. Además, obtenemos un enfoque estándar en el despliegue (aunque sea utilizando NFS y montando directorios adicionales).
Resultado
¿Qué veremos cuando apliquemos la configuración preparada?
En la solicitud de fusión aparecerá una estadística general de las pruebas realizadas en su última canalización:

Aquí se puede hacer clic en cada error para obtener más detalles:

NB: El lector atento notará que estamos probando una aplicación NodeJS, y en las capturas de pantalla — .NET… No te sorprendas: simplemente, en el transcurso de la preparación del artículo, no se encontraron errores en la prueba de la primera aplicación, pero sí en la otra.
Conclusión
Como se puede ver, ¡nada complicado!
En principio, si ya tienes un construidor shell y está funcionando, y no necesitas Kubernetes, añadir pruebas a él será una tarea aún más sencilla que la descrita aquí. Y en encontrarás ejemplos para Ruby, Go, Gradle, Maven y algunos otros.
P.D.
También puedes leer en nuestro blog:
- «»;
- «»;
- «».
Fuente: habr.com
