Obwohl alle genau wissen, wie wichtig es ist, ihre Software zu testen, und viele dies bereits automatisiert tun, fand sich in den Weiten von Habr kein einziges Rezept zur Einrichtung der Verbindung zwischen so populĂ€ren Produkten in dieser Nische wie (unserem Favoriten) GitLab und JUnit. Lassen Sie uns diese LĂŒcke schlieĂen!

EinfĂŒhrung
ZunÀchst möchte ich den Kontext festlegen:
- Da alle unsere Anwendungen in Kubernetes laufen, werden wir die AusfĂŒhrung von Tests in der entsprechenden Infrastruktur behandeln.
- FĂŒr den Bau und das Deployment verwenden wir (im Hinblick auf infrastrukturelle Komponenten bedeutet dies auch automatisch, dass Helm eingesetzt wird).
- Auf die Einzelheiten der Erstellung von Tests werde ich nicht eingehen: In unserem Fall schreibt der Kunde die Tests selbst, und wir sorgen lediglich fĂŒr deren AusfĂŒhrung (und fĂŒr das Vorhandensein des entsprechenden Berichts im Merge Request).
Wie wird der gesamte Ablauf aussehen?
- Der Aufbau der Anwendung â die Beschreibung dieses Schrittes lassen wir weg.
- Deployment der Anwendung in einen separaten Namespace des Kubernetes-Clusters und DurchfĂŒhrung der Tests.
- Suche nach Artefakten und Parsing des JUnit-Berichts durch GitLab.
- Löschen des zuvor erstellten Namespaces.
Jetzt â zur Umsetzung!
Einstellungen
GitLab CI
Beginnen wir mit dem Abschnitt .gitlab-ci.yaml, der das Deployment der Anwendung und die AusfĂŒhrung der Tests beschreibt. Das Listing ist ziemlich umfangreich geworden und wurde daher mit Kommentaren ausfĂŒhrlich ergĂ€nzt:
variablen:
# wir erklÀren die werf-Version, die wir verwenden möchten
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
Skript:
# wir erstellen einen Namespace in K8s, falls dieser nicht vorhanden ist
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# wir laden werf und deployen â nĂ€here Informationen dazu finden Sie in der Dokumentation
# (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:-''}"
# wir ĂŒbergeben die Variable `run_tests`
# sie wird im Rendern des Helm-Releases verwendet
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# wir Ă€ndern das Timeout (es gibt lange Tests) und ĂŒbergeben es an das Release
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
AbhÀngigkeiten:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# wir erstellen ein Verzeichnis fĂŒr den zukĂŒnftigen Bericht, basierend auf $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# ein notwendiger Workaround, da GitLab Artefakte in seinem build-dir haben möchte
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# nach Abschluss der Tests löschen wir das Release zusammen mit dem Job
# (und vielleicht seiner Infrastruktur)
- 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
# wir erlauben FehlschlÀge, aber Sie können es anders machen
allow_failure: true
variablen:
RUN_TESTS: 'ja'
# wir setzen den Kontext in werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# wir verwenden einen Runner mit dem Tag `werf-runner`
- werf-runner
artefakte:
# es ist erforderlich, ein Artefakt zu erstellen, damit es im Pipeline sichtbar ist
# und heruntergeladen werden kann â z.B. fĂŒr eine detailliertere Untersuchung
Pfade:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# Artefakte, die Àlter als eine Woche sind, werden gelöscht
expire_in: 7 Tage
# wichtig: diese Zeilen sind fĂŒr das Parsen des Berichts durch GitLab verantwortlich
Berichte:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# zur Vereinfachung sind hier nur zwei Phasen dargestellt
# in der RealitĂ€t werden es mehr sein â mindestens wegen des Deployments
stufen:
- build
- tests
build:
phase: build
Skript:
# der Build â wiederum gemÀà der Dokumentation zu 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
auĂer:
- ZeitplÀne
testen:
<<: *test-base
Umgebung:
# "der eigentliche Kern" der Namensgebung des Namespaces
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
Name: tests-${CI_COMMIT_REF_SLUG}
Phase: tests
auĂer:
- ZeitplÀneKubernetes
Jetzt im Verzeichnis .helm/templates erstellen wir eine YAML mit einem Job â tests-job.yaml â um Tests zu starten und die dafĂŒr benötigten Ressourcen von Kubernetes bereitzustellen. Hinweise siehe nach der Auflistung:
{{- 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 }} Welche Ressourcen sind in dieser Konfiguration beschrieben? Bei der Bereitstellung erstellen wir einen einzigartigen Namespace fĂŒr das Projekt (dies wird auch noch angegeben in .gitlab-ci.yaml â tests-${CI_COMMIT_REF_SLUG}) und versehen diesen mit:
- ConfigMap einem Tests-Skript;
- Job einer Beschreibung des Pods und der spezifischen Direktive
command, die die Tests tatsÀchlich startet; - PV und PVC, die das Speichern von Testdaten ermöglichen.
Bitte beachten Sie die Bedingung am Anfang des Manifests â entsprechend mĂŒssen andere YAML-Dateien des Helm-Charts mit der Anwendung in if eine umgekehrte Struktur eingeschlossen werden, damit sie beim Testen nicht bereitgestellt werden. Das bedeutet: {{- if ne .Values.global.run_tests "yes" }} --- ich eine andere yaml {{- end }}
Wenn die Testseine bestimmte Infrastruktur erfordern zum Beispiel Redis, RabbitMQ, Mongo, PostgreSQLâŠ) â deren YAML-Dateien können (zum Beispiel Redis, RabbitMQ, Mongo, PostgreSQLâŠ) â deren YAMLs können nicht Ausschalten. Erweitern Sie sie in der Testumgebung⊠natĂŒrlich, indem Sie sie nach Belieben anpassen.
Der letzte Schliff
Da das Bauen und Bereitstellen mit werf bisher funktioniert nur auf dem Build-Server (mit gitlab-runner), wĂ€hrend das Pod mit den Tests auf dem Master gestartet wird, mĂŒssen Sie ein Verzeichnis erstellen /mnt/tests auf dem Master und es dem Runner zur VerfĂŒgung stellen, zum Beispiel ĂŒber NFS. Ein erweitertes Beispiel mit ErklĂ€rungen finden Sie in der .
Das Ergebnis wird sein:
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 0Niemand hindert Sie daran, auch einen NFS-Share direkt auf dem gitlab-runner zu erstellen und ihn in die Pods einzuhÀngen.
Hinweis
Vielleicht fragen Sie sich, warum man alles durch die Erstellung eines Jobs komplizieren sollte, wenn man einfach das Skript mit den Tests direkt auf dem Shell-Runner ausfĂŒhren kann? Die Antwort ist ziemlich trivial...
Einige Tests erfordern den Zugriff auf die Infrastruktur (MongoDB, RabbitMQ, PostgreSQL usw.), um die ordnungsgemĂ€Ăe Funktionsweise zu ĂŒberprĂŒfen. Wir machen das Testen einheitlich â mit diesem Ansatz wird es einfach, solche zusĂ€tzlichen EntitĂ€ten einzubeziehen. DarĂŒber hinaus erhalten wir den Standard- eine Vorgehensweise beim Deployment (auch wenn es mit NFS und zusĂ€tzlichem Mounten von Verzeichnissen erfolgt).
Ergebnis
Was werden wir sehen, wenn wir die vorbereitete Konfiguration anwenden?
Im Merge-Request wird eine Zusammenfassungsstatistik zu den Tests angezeigt, die in der letzten Pipeline ausgefĂŒhrt wurden:

Auf jeden Fehler kann hier geklickt werden, um Details zu erhalten:

NB: Aufmerksame Leser werden bemerken, dass wir eine NodeJS-Anwendung testen, und auf den Screenshots ist .NET zu sehen⊠Lassen Sie sich nicht ĂŒberraschen: Im Rahmen der Vorbereitung des Artikels gab es keine Fehler beim Testen der ersten Anwendung, jedoch wurden in einer anderen gefunden.
Fazit
Wie man sieht, nichts Kompliziertes!
Im Grunde genommen, wenn Sie bereits einen Shell-Bausatz haben und er funktioniert, wĂ€hrend Sie Kubernetes nicht benötigen â wird das Testen daran eine noch einfachere Aufgabe sein als die hier beschriebene. Und in der finden Sie Beispiele fĂŒr Ruby, Go, Gradle, Maven und einige andere.
P.S.
Lesen Sie auch in unserem Blog:
- «»;
- «»;
- «».
Quelle: habr.com
