Nonostante tutti sappiano che è importante e necessario testare il proprio software e che molti lo fanno già in modo automatico, su Habr non è stato trovato alcun ricetta per configurare l'integrazione di prodotti così popolari in questo settore, come (nostro amato) GitLab e JUnit. Colmiamo questa lacuna!

Introduzione
Per iniziare, delineerò il contesto:
- Poiché tutte le nostre applicazioni funzionano in Kubernetes, verrà esaminato l'avvio dei test nell'infrastruttura corrispondente.
- Per la costruzione e il deployment, utilizziamo (in termini di componenti infrastrutturali, questo significa anche che viene utilizzato Helm).
- Non entrerò nei dettagli della creazione dei test: nel nostro caso, il cliente scrive i test da solo e noi ci limitiamo a garantirne l'esecuzione (e l'esistenza di un report corrispondente nella merge request).
Quale sarà la sequenza generale delle azioni?
- La costruzione dell'applicazione - salteremo la descrizione di questo passaggio.
- Il deployment dell'applicazione in un namespace separato del cluster Kubernetes e l'avvio del testing.
- Ricerca degli artefatti e parsing del report JUnit da parte di GitLab.
- Cancellazione del namespace creato in precedenza.
Ora, passiamo all'implementazione!
Configurazione
GitLab CI
Iniziamo con il frammento .gitlab-ci.yaml, che descrive il deployment dell'applicazione e l'avvio dei test. Il listing è piuttosto voluminoso, quindi è stato ampiamente arricchito di commenti:
variabili:
# dichiariamo la versione di werf che intendiamo utilizzare
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# creiamo un namespace in K8s, se non esiste
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# scarichiamo werf e deploy — per ulteriori informazioni consultare la documentazione
# (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:-''}"
# passiamo la variabile `run_tests`
# sarà utilizzata nel rendering del rilascio Helm
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# cambiamo il timeout (ci possono essere test lunghi) e lo passiamo al rilascio
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# creiamo una directory per il futuro rapporto, in base a $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# un workaround necessario, poiché GitLab vuole ricevere gli artefatti nella sua build-dir
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# dopo la fine dei test, eliminiamo il rilascio insieme al Job
# (e, possibilmente, alla sua infrastruttura)
- 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
# consentiamo i fallimenti, ma potete fare diversamente
allow_failure: true
variables:
RUN_TESTS: 'yes'
# impostiamo il contesto in werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# utilizziamo il runner con il tag `werf-runner`
- werf-runner
artifacts:
# è necessario raccogliere l'artefatto affinché possa essere visto
# nel pipeline e scaricato — per uno studio più attento
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# gli artefatti più vecchi di una settimana saranno eliminati
expire_in: 7 day
# importante: queste righe sono responsabili dell'analisi del rapporto da parte di GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# per semplificare sono mostrate solo due fasi
# in realtà ne avrete di più — almeno a causa del deploy
stages:
- build
- tests
build:
stage: build
script:
# build — di nuovo secondo la documentazione su 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
esegui test:
<<: *test-base
environment:
# "il vero nome" del namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Ora nella directory .helm/templates creiamo un YAML con il Job — tests-job.yaml — per l'esecuzione dei test e le risorse Kubernetes necessarie. Le spiegazioni sono disponibili dopo il 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 }} Quali risorse sono descritte in questa configurazione? Durante il deploy creiamo un namespace unico per il progetto (questo è specificato anche in .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) e in esso rilasciamo:
- ConfigMap con lo script di test;
- Job con la descrizione del pod e la direttiva specificata
command, che avvia effettivamente i test; - PV e PVC, che consentono di memorizzare i dati dei test.
Si prega di notare la condizione di input con if all'inizio del manifesto — di conseguenza, altri file YAML del chart Helm con l'app devono essere racchiusi in una controparte costruzione, affinché non siano rilasciati durante il test. Cioè:
{{- if ne .Values.global.run_tests "yes" }}
---
e un altro yaml
{{- end }}Tuttavia, se i test richiedono un'infrastruttura particolare. (ad esempio, Redis, RabbitMQ, Mongo, PostgreSQL…) — i loro YAML possono non essere disattivati. Espandili e anche loro nell'ambiente di test… naturalmente, modificandoli a tuo piacimento.
Ultimo ritocco
Poiché la costruzione e il deployment tramite werf attualmente funzionano solo sul server di build (con gitlab-runner), e il pod con i test viene eseguito sul master, sarà necessario creare una directory /mnt/tests sul master e fornirla al runner, ad esempio, tramite NFS. Un esempio distribuito con spiegazioni può essere trovato nella .
Il risultato sarà:
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 0Nessuno ha vietato di creare una condivisione NFS direttamente su gitlab-runner, dopo di che montarla nei pod.
Nota
Potresti chiederti perché complicare tutto creando un Job, se è possibile semplicemente eseguire uno script con i test direttamente sul runner shell? La risposta è piuttosto triviale…
Alcuni test richiedono l'interazione con l'infrastruttura (MongoDB, RabbitMQ, PostgreSQL, ecc.) per verificare la correttezza del funzionamento con esse. Rendiamo i test uniformi: con questo approccio includere tali entità aggiuntive diventa facile. Inoltre, otteniamo un approccio standard al deployment (anche se con l'uso di NFS e montaggio aggiuntivo delle directory).
Risultato
Cosa vedremo quando applichiamo la configurazione preparata?
Nel merge request verrà mostrata una statistica riepilogativa sui test eseguiti nel suo ultimo pipeline:

Cliccando su ogni errore qui, si possono ottenere dettagli:

NB: Il lettore attento noterà che stiamo testando un'applicazione NodeJS, mentre negli screenshot c'è .NET… Non sorprenderti: semplicemente nella preparazione dell'articolo non ci sono stati errori nel testing della prima applicazione, ma ne sono stati trovati in un'altra.
Conclusione
Come si può vedere, non c'è niente di complicato!
Fondamentalmente, se hai già un costruttore shell e funziona, e Kubernetes non ti serve, aggiungere i test sarà un compito ancora più semplice rispetto a quanto descritto qui. Inoltre, nella troverai esempi per Ruby, Go, Gradle, Maven e alcuni altri.
P.S.
Leggi anche nel nostro blog:
- «»;
- «»;
- «».
Fonte: habr.com
