Neskatoties uz to, ka visi labi zina, cik svarīgi ir testēt savu programmatūru, un daudzi to jau sen dara automātiski, Habrā neatradās neviens receptes par šo populāro produktu, kā (mūsu mīļotais) GitLab un JUnit, savienošanu. Aizpildīsim šo tukšumu!

Ievada
Vispirms iezīmēšu kontekstu:
- Tā kā visas mūsu lietojumprogrammas darbojas Kubernetes, tiks apskatīta testu palaišana atbilstošajā infrastruktūrā.
- Mēs izmantojam (infrastruktūras komponentu ziņā tas automātiski nozīmē, ka tiek izmantots arī Helm).
- Nedziļināšos detaļās par testu izveidi: mūsu gadījumā klients raksta testus pats, un mēs tikai nodrošinām to palaišanu (un atbilstoša ziņojuma esamību merge request’ā).
Kāda būs vispārējā darbību secība?
- Lietojumprogrammas izveide — šī posma aprakstu mēs izlaidīsim.
- Lietojumprogrammas izvietošana atsevišķā Kubernetes klastera namespace un testēšanas uzsākšana.
- Artefaktu meklēšana un JUnit ziņojuma parsēšana ar GitLab.
- Iepriekš izveidotā namespace dzēšana.
Tagad — pie īstenošanas!
Iestatīšana
GitLab CI
Sāksim ar fragmentu .gitlab-ci.yaml, kas apraksta lietojumprogrammas izvietošanu un testu palaišanu. Saraksts izrādījās diezgan apjomīgs, tāpēc būtiski papildināts ar komentāriem:
mainītāji:
# nosakām werf versiju, ko plānojam izmantot
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
skripts:
# izveidojam namespace K8s, ja tāda nav
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# lejupielādējam werf un veicam izvietošanu — sīkāk par to skatiet dokumentācijā
# (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:-''}"
# nododam mainīgo `run_tests`
# tas tiks izmantots Helm izlaiduma sakārtošanā
--set "global.run_tests=${RUN_TESTS:-nē}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# mainām laika ierobežojumu (dažreiz testi ir gari) un nododam to izlaidumam
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
atkarības:
- Build
.test-base: &test-base
pagarinājums: .base_deploy
before_script:
# izveidojam direktoriju nākotnes atskaitēm, atkarībā no $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# piespiedu risinājums, jo GitLab vēlas saņemt artefaktus savā build-dir’ā
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# pēc testu pabeigšanas izdzēšam izlaidumu kopā ar Job’iem
# (un, iespējams, tā infrastruktūru)
- 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
# mēs atļaujam kļūdas, bet jūs varat darīt pretēji
atļaut_kļūdas: true
mainītāji:
RUN_TESTS: 'jā'
# nosakām kontekstu werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
etiķetes:
# izmantojam runneri ar etiķeti `werf-runner`
- werf-runner
artefakti:
# nepieciešams savākt artefaktu, lai to varētu redzēt
# pipeline un lejupielādēt — piemēram, dziļākai izpētei
ceļi:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakti, kas ir vecāki par nedēļu, tiks izdzēsti
expire_in: 7 dienas
# svarīgi: šīs rindas atbild par atskaites parsēšanu GitLab’ā
ziņojumi:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# vienkāršošanai šeit parādītas tikai divas fāzes
# patiesībā jums tās būs vairāk — vismaz dēļ izvietošanas
fāzes:
- build
- tests
build:
fāze: build
skripts:
# būvēšana — vēlreiz pēc dokumentācijas par 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
etiķetes:
- werf-runner
izņēmumi:
- grafiki
veikt testus:
<<: *test-base
vide:
# "pašas sāls" namespace nosaukumā
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
nosaukums: tests-${CI_COMMIT_REF_SLUG}
fāze: tests
izņēmumi:
- grafikiKubernetes
Tagad direktorijā .helm/templates izveidosim YAML ar Job’iem — tests-job.yaml — testu palaišanai un nepieciešamajiem Kubernetes resursiem. Skatiet paskaidrojumus pēc saraksta:
{{- 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 }} Kādi resursi ir aprakstīti šajā konfigurācijā? Izvietojot, izveidojam unikālu projekta namespace (tas norādīts vēl .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) un izplatām to:
- ConfigMap ar testa skriptu;
- Job ar pod’a aprakstu un norādīto direktīvu
command, kas tieši palaiž testus; - PV un PVC, kas ļauj glabāt testu datus.
Lūdzu, ņemiet vērā ievada nosacījumu ar if sākumā manifesta — attiecīgi, citiem YAML failiem Helm chartā ar lietojumu jābūt iekļautiem pretējā struktūrā, lai tie netiktu izvietoti testēšanas laikā. Tātad:
{{- if ne .Values.global.run_tests "yes" }}
---
viens cits yaml fails
{{- end }}Tomēr, ja testi prasa zināmu infrastruktūru (piemēram, Redis, RabbitMQ, Mongo, PostgreSQL…) — to YAML failus var nees izslēgt. Izvērst tos testēšanas vidē… protams, pielāgojot pēc saviem ieskatiem.
Pēdējais akords
Jo būvēšana un izvietošana ar werf pagaidām darbojas tikai uz build-servera (ar gitlab-runner), bet pods ar testiem tiek palaists uz meistara, būs jāizveido katalogs /mnt/tests uz meistara un jānodod to runnerim, piemēram, pa NFS. Izvērsts piemērs ar paskaidrojumiem atrodamies .
Rezultāts būs:
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 0Neviens ne aizliedz izveidot NFS-share tieši uz gitlab-runner, pēc tam to montēt pod’os.
Piezīme
Iespējams, jūs jautājat, kāpēc vispār sarežģīt ar Job’iem, ja var vienkārši palaist skriptu ar testiem tieši uz shell-runnera? Atbilde ir diezgan vienkārša…
Daži testi prasa piekļuvi infrastruktūrai (MongoDB, RabbitMQ, PostgreSQL u.c.) lai pārbaudītu pareizu darbību ar tām. Mēs veicam testēšanu standartizētā veidā — šādā pieejā papildus entitātes iekļaut ir viegli. Turklāt mēs iegūstam standarta piekļuvi izvietošanai (lai arī ar NFS, papildu katalogu montēšanu).
Rezultāts
Ko mēs redzēsim, kad izmantosim sagatavoto konfigurāciju?
Merging requestā tiks parādīta kopsavilkuma statistika par testiem, kas palaisti tā pēdējā pipeline:

Uz katru kļūdu šeit var noklikšķināt, lai iegūtu detaļas:

P.S.: Rūpīgs lasītājs pamanīs, ka mēs testējam NodeJS lietojumprogrammu, bet ekrānšāviņos ir .NET… Neuztraucieties: vienkārši rakstīšanas procesa ietvaros netika atrastas kļūdas pirmajā lietojumprogrammā, bet atradām tās otrajā.
Secinājums
Kā redzams, nekas sarežģīts!
Principā, ja jums jau ir shell-būvējs un tas darbojas, bet Kubernetes nav nepieciešams — pievienot tam testēšanu būs vēl vieglāka uzdevuma nekā šeit aprakstīts. Savukārt atradīsiet piemērus Ruby, Go, Gradle, Maven un citām valodām.
P.S.
Lasiet arī mūsu emuāra rakstos:
- «»;
- «»;
- «».
Avots: habr.com
