På trods af at alle ved, at det er vigtigt at teste sin software, og mange allerede gør det automatisk, er der ikke fundet nogen opskrift på Habr om opsætning af kombinationen af populære producenter i denne niche, såsom (vores favorit) GitLab og JUnit. Lad os udfylde dette hul!

Indledende
Lad mig først skitsere konteksten:
- Da alle vores applikationer kører i Kubernetes, vil vi se på kørsel af tests i den tilsvarende infrastruktur.
- Til bygning og deployment bruger vi (i forhold til infrastrukturkomponenter betyder dette også automatisk, at Helm er involveret).
- Jeg vil ikke gå i dybden med oprettelsen af tests: i vores tilfælde skriver kunden selv tests, og vi sikrer blot deres kørsel (og tilstedeværelsen af den relevante rapport i merge request’en).
Hvordan vil den overordnede sekvens af handlinger se ud?
- Bygning af applikationen - vi springer beskrivelsen af dette trin over.
- Deployment af applikationen til et separat namespace i Kubernetes-klusteret og start af testningen.
- Søgning efter artefakter og parsing af JUnit-rapporten af GitLab.
- Sletning af det tidligere oprettede namespace.
Nu - til implementeringen!
Indstillinger
GitLab CI
Lad os starte med fragmentet .gitlab-ci.yaml, som beskriver deployment af applikationen og kørsel af tests. Udkastet blev ret omfattende, så det er grundigt suppleret med kommentarer:
variabler:
# erklærer den version af werf, som vi vil bruge
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# opretter et namespace i K8s, hvis det ikke allerede findes
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# downloader werf og deployer — læs mere om dette i dokumentationen
# (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:-''}"
# overfører variablen `run_tests`
# den vil blive brugt i renderingen af Helm-udgivelsen
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# ændrer timeout (tests kan tage lang tid) og overfører den til udgivelsen
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# opretter en mappe til den kommende rapport, baseret på $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# en tvungen workaround, da GitLab ønsker at modtage artefakter i sin build-mappe
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# efter testene afsluttes, fjerner vi udgivelsen sammen med jobbet
# (og måske dens 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
# vi tillader fejl, men du kan vælge anderledes
allow_failure: true
variables:
RUN_TESTS: 'yes'
# sætter konteksten i werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# bruger runneren med tagget `werf-runner`
- werf-runner
artifacts:
# kræves at genere artefakten for at kunne ses
# i pipeline og downloades — f.eks. til mere grundig analyse
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# artefakter ældre end en uge vil blive fjernet
expire_in: 7 day
# vigtigt: disse linjer styrer parsing af rapporten af GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# for enkelhed vises kun to faser her
# i virkeligheden vil du have flere — mindst på grund af deployment
stages:
- build
- tests
build:
stage: build
script:
# bygning — endnu engang henvises til dokumentationen for 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
afvikle tests:
<<: *test-base
environment:
# "selve navnet" på namespace’et
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Nu i mappen .helm/templates opretter vi en YAML med Job'ens detaljer — tests-job.yaml — til at køre tests og de nødvendige ressourcer fra Kubernetes. Forklaringer ses efter listen:
{{- 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 }} Hvilke ressourcer er beskrevet i denne konfiguration? Ved deployment opretter vi et unikt namespace til projektet (det er angivet tidligere i .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) og vi distribuerer til det:
- ConfigMap med testscriptet;
- Job med beskrivelsen af pod'en og den angivne direktiv
command, som netop starter testene; - PV og PVC, der gør det muligt at gemme testdata.
Bemærk det indledende betingelse med if i starten af manifestet — andre YAML-filer i Helm-diagrammet skal derfor indhylles i den omvendte konstruktion, så de ikke distribueres under testning. Det vil sige:
{{- if ne .Values.global.run_tests "yes" }}
---
jeg en anden yaml
{{- end }}Men hvis testene kræver en vis infrastruktur (for eksempel, Redis, RabbitMQ, Mongo, PostgreSQL…) — deres YAML-filer kan ikke slukkes. Udrul dem også i et testmiljø… selvfølgelig justeret som du ønsker.
Den afsluttende touch
Da opbygning og deployment ved hjælp af werf stadig fungerer kun på build-serveren (med gitlab-runner), og pod'en med testene køres på masteren, skal der oprettes et bibliotek /mnt/tests på masteren og gøres tilgængeligt for runneren, for eksempel via NFS. Et udrullet eksempel med forklaringer kan findes i .
Resultatet vil være:
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 0Ingen forhindrer dig i at lave et NFS-share direkte på gitlab-runner'en og derefter montere det i pod'erne.
Bemærk
Måske spørger du, hvorfor overhovedet komplicere med at oprette en Job, når man blot kan køre et script med testene direkte på shell-runneren? Svaret er ganske trivielt...
Nogle tests kræver adgang til infrastrukturen (MongoDB, RabbitMQ, PostgreSQL osv.) for at kontrollere korrektheden af interaktionerne. Vi gør testningen ensartet — med denne tilgang bliver det let at inkludere sådanne ekstra enheder. Derudover opnår vi standard tilgang i deployment (selv med brug af NFS, med yderligere montering af mapper).
Resultat
Hvad ser vi, når vi anvender den forberedte konfiguration?
I merge request'en vil der blive vist en oversigt over statistikken for tests, der er kørt i dens seneste pipeline:

For hver fejl her kan du klikke for at få detaljer:

NB: Den opmærksomme læser vil bemærke, at vi tester en NodeJS-applikation, men på skærmbillederne er det .NET… Bliv ikke overrasket: blot fordi der ikke var fejl i testningen af den første applikation i artikelens forberedelse, fandt vi dem i den anden.
Konklusion
Som det kan ses, er der ikke noget kompliceret!
I princippet, hvis du allerede har en shell-builder og den fungerer, og Kubernetes ikke er nødvendig for dig — at koble testning til den vil være en endnu enklere opgave end den, der er beskrevet her. Og i vil du finde eksempler for Ruby, Go, Gradle, Maven og nogle andre.
P.S.
Læs også på vores blog:
- «»;
- «»;
- «».
Kilde: habr.com
