Bien que tout le monde sache qu'il est important et nécessaire de tester son logiciel, et que beaucoup le font automatiquement depuis longtemps, il n'y avait pas de recette sur Habr pour configurer l'intégration de produits aussi populaires dans ce domaine que (notre préféré) GitLab et JUnit. Remédions à cela !

Introduction
Pour commencer, définissons le contexte :
- Comme toutes nos applications fonctionnent sur Kubernetes, nous examinerons le lancement des tests dans l'infrastructure correspondante.
- Pour la construction et le déploiement, nous utilisons (dans le sens des composants d'infrastructure, cela signifie également que Helm est impliqué).
- Je ne vais pas entrer dans les dĂ©tails de la crĂ©ation des tests : dans notre cas, le client Ă©crit les tests lui-mĂȘme, et nous nous contentons d'assurer leur exĂ©cution (et de fournir le rapport correspondant dans la demande de fusion).
à quoi ressemblera la séquence générale des actions ?
- Compilation de l'application â nous allons sauter cette Ă©tape.
- Déploiement de l'application dans un namespace séparé du cluster Kubernetes et lancement des tests.
- Recherche des artefacts et analyse du rapport JUnit par GitLab.
- Suppression du namespace créé précédemment.
Passons maintenant Ă la mise en Ćuvre !
Configuration
GitLab CI
Commençons par le fragment .gitlab-ci.yaml, qui décrit le déploiement de l'application et le lancement des tests. Le listing est assez volumineux, donc considérablement enrichi de commentaires :
variables:
# déclarons la version de werf que nous allons utiliser
WERF_VERSION: "1.0 bĂȘta"
.base_deploy: &base_deploy
script:
# créons un namespace dans K8s, s'il n'existe pas
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# chargeons werf et dĂ©ployons â pour plus de dĂ©tails, voir la documentation
# (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:-''}"
# transmettons la variable `run_tests`
# elle sera utilisée dans le rendu du Helm release
--set "global.run_tests=${RUN_TESTS:-non}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# modifions le timeout (certaines tests peuvent prendre longtemps) et le transmettons dans le release
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# créons un répertoire pour le futur rapport, basé sur $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# contournement nécessaire, car GitLab souhaite recevoir des artefacts dans son build-dir
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# Ă la fin des tests, supprimons le release avec le Job
# (et, peut-ĂȘtre, son infrastructure)
- 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
# nous autorisons les échecs, mais vous pouvez faire autrement
allow_failure: true
variables:
RUN_TESTS: 'oui'
# définissons le contexte dans werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# utilisons un runner avec le tag `werf-runner`
- werf-runner
artifacts:
# nécessite la collecte d'artefacts pour pouvoir les voir
# dans le pipeline et tĂ©lĂ©charger â par exemple, pour une Ă©tude plus approfondie
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# les artefacts de plus d'une semaine seront supprimés
expire_in: 7 jours
# important : ces lignes sont responsables du parsing du rapport par GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# pour simplifier, seulement deux étapes sont montrées ici
# en rĂ©alitĂ©, il y en aura plus â au minimum Ă cause du dĂ©ploiement
stages:
- build
- tests
build:
stage: build
script:
# construction â encore selon la documentation 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:
# "la véritable essence" de la nomination du namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Maintenant dans le rĂ©pertoire .helm/templates crĂ©ons un YAML avec un Job â tests-job.yaml â pour exĂ©cuter des tests et les ressources nĂ©cessaires Ă Kubernetes. Les explications se trouvent aprĂšs le listing :
{{- 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 }} Quels sont les ressources dĂ©crites dans cette configuration ? Lors du dĂ©ploiement, nous crĂ©ons un namespace unique pour le projet (cela est indiquĂ© encore dans .gitlab-ci.yaml â tests-${CI_COMMIT_REF_SLUG}) et nous y dĂ©ployons :
- ConfigMap avec le script de test ;
- Job avec la description du pod et la directive indiquée
commande, qui lance justement les tests ; - PV et PVC, qui permettent de stocker les données des tests.
Notez la condition d'entrĂ©e avec if au dĂ©but du manifeste â par consĂ©quent, d'autres fichiers YAML du chart Helm avec l'application doivent ĂȘtre enveloppĂ©s dans la structure inversĂ©e , pour ne pas ĂȘtre dĂ©ployĂ©s lors des tests. En d'autres termes :
{{- if ne .Values.global.run_tests "yes" }}
---
j'ai un autre fichier yaml
{{- end }}NĂ©anmoins, si les tests nĂ©cessitent une certaine infrastructure (par exemple, Redis, RabbitMQ, Mongo, PostgreSQLâŠ) â leurs YAML peuvent ĂȘtre ne DĂ©sactivez-les. DĂ©ployez-les dans un environnement de test⊠bien sĂ»r, en les ajustant Ă votre convenance.
DerniĂšre touche
Ătant donnĂ© que la construction et le dĂ©ploiement Ă l'aide de werf fonctionnent encore uniquement sur le serveur de build (avec gitlab-runner), et que le pod pour les tests se lance sur le master, il sera nĂ©cessaire de crĂ©er un rĂ©pertoire /mnt/tests sur le master et de le donner au runner, par exemple, via NFS. Un exemple dĂ©ployĂ© avec des explications peut ĂȘtre trouvĂ© dans .
Le résultat sera :
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 0Personne n'interdit de créer un partage NFS directement sur le gitlab-runner, puis de le monter dans les pods.
Remarque
Vous vous demandez peut-ĂȘtre pourquoi compliquer les choses en crĂ©ant un Job, si vous pouvez simplement lancer un script avec les tests directement sur le shell-runner ? La rĂ©ponse est assez trivialeâŠ
Certains tests nĂ©cessitent d'accĂ©der Ă l'infrastructure (MongoDB, RabbitMQ, PostgreSQL, etc.) pour vĂ©rifier leur bon fonctionnement. Nous rendons les tests standardisĂ©s â avec cette approche, il devient facile d'intĂ©grer de telles entitĂ©s supplĂ©mentaires. De plus, nous obtenons niveau une approche dans le dĂ©ploiement (mĂȘme avec l'utilisation de NFS et le montage supplĂ©mentaire de rĂ©pertoires).
Résultat
Que verrons-nous lorsque nous appliquerons la configuration préparée ?
Dans la demande de fusion, des statistiques récapitulatives des tests exécutés dans son dernier pipeline seront affichées :

Pour chaque erreur, vous pouvez cliquer ici pour obtenir des détails :

NB: Le lecteur attentif remarquera que nous testons une application NodeJS, alors que les captures d'écran montrent .NET⊠Ne soyez pas étonnés : simplement, dans le cadre de la préparation de l'article, aucune erreur n'a été trouvée dans le test du premier application, mais des erreurs ont été détectées dans une autre.
Conclusion
Comme vous pouvez le voir, rien de compliqué !
En principe, si vous avez dĂ©jĂ un assembleur shell et qu'il fonctionne, et que Kubernetes ne vous est pas nĂ©cessaire â l'ajout de tests sera une tĂąche encore plus simple que celle dĂ©crite ici. Et dans vous trouverez des exemples pour Ruby, Go, Gradle, Maven et quelques autres.
P.S.
Lisez aussi dans notre blog :
- «»;
- «»;
- «».
Source : habr.com
