Kljub temu da vsi vedo, da je testiranje programske opreme pomembno in nujno, ter da mnogi to že dolgo počnejo avtomatsko, na Habrju ni bilo najti nobenega recepta za nastavitev povezave med tako priljubljenima produktoma na tem področju, kot sta (naša ljubljena) GitLab in JUnit. Zapolnimo to vrzel!

Uvodne informacije
Najprej bom določil kontekst:
- Ker vse naše aplikacije delujejo v Kubernetesu, bomo obravnavali zagon testov v ustrezni infrastrukturi.
- Za gradnjo in namestitev uporabljamo (kar pomeni, da to infrastrukturne komponente avtomatsko vključujejo tudi Helm).
- Ne bom se poglabljal v podrobnosti neposrednega ustvarjanja testov: v našem primeru testov ne pišejo naši inženirji, temveč jih stranka sama, mi pa zgolj omogočimo njihov zagon (in zagotovimo, da je ustrezen poročilo v merge requestu).
Kako bo izgledal celoten postopek?
- Gradnja aplikacije — opis tega koraka bomo izpustili.
- Namestitev aplikacije v ločen namespace Kubernetes klasterja in zagon testiranja.
- Iskanje artefaktov in razčlenitev JUnit poročila s strani GitLaba.
- Izbris prej ustvarjenega namespacea.
Sedaj pa k implementaciji!
Setup
GitLab CI
Začnimo s fragmentom .gitlab-ci.yaml, ki opisuje namestitev aplikacije in zagon testov. Seznam je precej obsežen, zato je obsežno dopolnjen s komentarji:
variables:
# zatvárame verziu werf, ktorú plánujeme použiť
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# vytvoríme namespace v K8s, ak neexistuje
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# nahrávame werf a deploy - viac o tom nájdete v dokumentácii
# (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:-''}"
# odovzdávame premennú `run_tests`
# bude sa používať pri renderovaní Helm releasu
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# meníme timeout (môžu byť dlhé testy) a odovzdávame ho do releasu
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# vytvoríme adresár pre budúcu správu, podľa $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# nútený workaround, pretože GitLab chce získať artefakty vo svojom build-dir
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# po skončení testov zmažeme release spolu s Jobom
# (a možno aj jeho infraštruktúrou)
- 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
# povolíme zlyhania, ale môžete to urobiť inak
allow_failure: true
variables:
RUN_TESTS: 'yes'
# určujeme kontext v werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# používame runner s tagom `werf-runner`
- werf-runner
artifacts:
# vyžaduje sa zostaviť artefakt, aby ho bolo možné vidieť
# v pipeline a stiahnuť - napríklad na podrobnejšie preskúmanie
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakty staršie ako týždeň budú odstránené
expire_in: 7 day
# dôležité: tieto riadky sú zodpovedné za parsovanie správy GitLabom
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# pre zjednodušenie sú tu zobrazené len dve fázy
# v skutočnosti ich budete mať viac - minimálne kvôli deploy
stages:
- build
- tests
build:
stage: build
script:
# zostavovanie - opäť podľa dokumentácie 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:
# "sama soľ" pomenovávanie namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Sedaj v direktoriju .helm/templates ustvarimo YAML z Jobom — tests-job.yaml — za spuščanje testov in potrebnimi viri Kubernetes. Razlago glejte po seznamu:
{{- 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 }} Kateri viri so opisani v tej konfiguraciji? Pri uvedbi ustvarjamo edinstven namespace za projekt (to je navedeno še v .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) in vanj izvajamo:
- ConfigMap s skriptom testiranja;
- Job s opisom pod-a in določeno direktivo
ukaz, ki pravzaprav запускает teste; - PV in PVC, ki omogočata shranjevanje podatkov testov.
Upoštevajte uvodno stanje z if na začetku manifesta — ustrezno, druge YAML datoteke Helm grafičnega prikaza aplikacije je treba zaviti v obratno konstruiranje, da se ne uvedejo med testiranjem. Torej:
{{- if ne .Values.global.run_tests "yes" }}
---
sem druga yml datoteka
{{- end }}Vendar pa, če testi zahtevajo določeno infrastrukturo (na primer, Redis, RabbitMQ, Mongo, PostgreSQL...) — njihove YAML datoteke je mogoče ne izklopiti. Razširite in jih v testnem okolju… seveda, z nekaj prilagoditvami po lastni izbiri.
Zaključni dotik
Ker gradnja in implementacija s pomočjo werf trenutno deluje le na gradbenem strežniku (s gitlab-runner), testni pod se zažene na masterju, bo treba ustvariti imenik /mnt/tests na masterju in ga predati runnerju, na primer, preko NFS. Razširjen primer s pojasnili lahko najdete v .
Rezultat bo:
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 0Nikdo ne prepoveduje, da bi NFS-share ustvarili neposredno na gitlab-runnerju, nato pa jo priklopili v pode.
Napomena
Morda se sprašujete, zakaj zapletati z ustvarjanjem Job-a, če lahko preprosto zaženete skript s testi prav na shell-runnerju? Odgovor je precej preprost…
Nekateri testi zahtevajo dostop do infrastrukture (MongoDB, RabbitMQ, PostgreSQL in podobno) za preverjanje pravilnosti delovanja z njimi. Testiranje izvajamo na poenoten način – pri tem pristopu je vključitev takih dodatnih entitet enostavna. Poleg tega pridobimo standardno pristop pri implementaciji (tudi z uporabo NFS, dodatnim montiranjem imenikov).
Результат.
Kaj bomo videli, ko uporabimo pripravljeno konfiguracijo?
V merge request-u bo prikazana povzetek statistik o testih, ki so bili izvedeni v njegovem zadnjem pipeline-u:

Na vsako napako tukaj lahko kliknete za pridobitev podrobnosti:

NB: Pozorni bralec bo opazil, da testiramo NodeJS-aplikacijo, na zaslonih pa je .NET… Ne presenečajte se: preprosto med pripravo članka ni bilo napak v testiranju prve aplikacije, smo pa jih našli pri drugi.
Zaključek
Kot lahko vidite, nič zapletenega!
Načeloma, če imate že shell-build in deluje, Kubernetes pa vam ni potreben – povezovanje testiranja bo še enostavnejša naloga kot tisto, opisano tukaj. In v boste našli primere za Ruby, Go, Gradle, Maven in nekaj drugih.
P.S.
Preberite tudi na našem blogu:
- «»;
- «»;
- «».
Izvor: habr.com
