Hoewel iedereen goed weet dat het belangrijk en noodzakelijk is om software te testen en velen dit al automatisch doen, is er op de ruimtes van Habr geen recept te vinden voor het instellen van de combinatie van populaire producten in deze niche, zoals (ons favoriete) GitLab en JUnit. We vullen deze leemte aan!

Inleiding
Om te beginnen zal ik de context schetsen:
- Aangezien al onze applicaties in Kubernetes draaien, zullen we de uitvoering van tests in de bijbehorende infrastructuur bekijken.
- Voor de bouw en implementatie gebruiken we (wat in termen van infrastructuurcomponenten ook automatisch betekent dat Helm wordt gebruikt).
- Ik zal niet in detail treden over het maken van tests: in ons geval schrijft de klant de tests zelf, en zorgen wij alleen voor de uitvoering ervan (en de aanwezigheid van een bijbehorende rapportage in de merge request).
Hoe ziet de algemene volgorde van handelingen eruit?
- De bouw van de applicatie — een beschrijving van deze fase laten we achterwege.
- Implementatie van de applicatie in een aparte namespace van het Kubernetes-cluster en uitvoering van de tests.
- Zoeken naar artefacten en parseren van het JUnit-rapport door GitLab.
- Verwijderen van de eerder aangemaakte namespace.
Nu — naar de implementatie!
Instellingen
GitLab CI
Laten we beginnen met het fragment .gitlab-ci.yaml, waarin de implementatie van de applicatie en het uitvoeren van de tests worden beschreven. De listing is vrij uitgebreid, daarom is deze grondig aangevuld met opmerkingen:
variabelen:
# geef de versie van werf aan die we gaan gebruiken
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# maak een namespace in K8s aan als deze nog niet bestaat
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# laad werf en implementeer — voor meer informatie hierover zie de documentatie
# (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:-''}"
# geef de variabele `run_tests`
# deze zal worden gebruikt bij het renderen van de Helm-release
--set "global.run_tests=${RUN_TESTS:-nee}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# wijzig de timeout (er kunnen lange tests zijn) en geef deze door aan de release
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# maak een directory aan voor het toekomstige rapport, gebaseerd op $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# dwingde workaround, omdat GitLab de artefacten in zijn build-directory wil ontvangen
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# verwijder de release samen met de Job na het einde van de tests
# (en mogelijk ook zijn infrastructuur)
- 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
# we staan falen toe, maar je kunt het ook anders doen
allow_failure: true
variables:
RUN_TESTS: 'ja'
# stel de context in voor werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# gebruik een runner met de tag `werf-runner`
- werf-runner
artifacts:
# het is vereist om een artefact te bouwen zodat deze zichtbaar en downloadbaar is
# in de pipeline — bijvoorbeeld voor een grondigere inspectie
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# artefacten ouder dan een week worden verwijderd
expire_in: 7 dagen
# belangrijk: deze regels zijn verantwoordelijk voor de parsing van het rapport door GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# ter vereenvoudiging zijn hier slechts twee fasen getoond
# in werkelijkheid heb je er meer — tenminste vanwege de implementatie
stages:
- build
- tests
build:
stage: build
script:
# build — opnieuw volgens de documentatie over 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:
# "de kern" van de naamgeving van de namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Nu in de directory .helm/templates creëren we een YAML met een Job — tests-job.yaml — voor het uitvoeren van tests en de benodigde Kubernetes-resources. Verklaringen zie na de 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 }} Welke resources worden in deze configuratie beschreven? Bij de deployment creëren we een unieke namespace voor het project (dit is al aangegeven in .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) en hierin implementeren we:
- ConfigMap met het testscrip;
- Job met de beschrijving van de pod en de opgegeven directieve
command, die juist de tests uitvoert; - PV en PVC, die het opslaan van testdata mogelijk maken.
Let op de invoervoorwaarde met als aan het begin van de manifest — dienovereenkomstig moeten andere YAML-bestanden van de Helm-chart met de applicatie worden ingepakt in de omgekeerde constructie, zodat ze niet worden gedeployed tijdens het testen. Dat wil zeggen:
{{- if ne .Values.global.run_tests "yes" }}
---
ik een andere yaml
{{- end }}Trouwens, als de tests bepaalde infrastructuur vereisen (bijv. Redis, RabbitMQ, Mongo, PostgreSQL…) — hun YAML's kunnen niet Uitschakelen. Breid ze uit en voer ze in een testomgeving uit… uiteraard, aangepast naar eigen inzicht.
Laatste details
Aangezien de build en deployment met behulp van werf tot nu toe werkt van op de build-server (met gitlab-runner), en de pod met tests start op de master, is het noodzakelijk om een directory te creëren /mnt/tests op de master en deze door te geven aan de runner, bijvoorbeeld via NFS. Een uitgebreid voorbeeld met uitleg is te vinden in de .
Het resultaat zal zijn:
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 staat het ook in de weg om een NFS-share op de gitlab-runner zelf te creëren en deze vervolgens in de pods te monteren.
Opmerking
Misschien vraag je je af, waarom al deze complicaties door het creëren van een Job, als je het script met tests gewoon op de shell-runner kunt uitvoeren? Het antwoord is vrij triviaal…
Sommige tests vereisen toegang tot de infrastructuur (MongoDB, RabbitMQ, PostgreSQL, enz.) om de juistheid van het werk ermee te controleren. We maken het testen uniform — met deze aanpak wordt het eenvoudig om dergelijke extra entiteiten erbij te betrekken. Bovendien krijgen we standaard een aanpak bij de deployment (zelfs al met gebruik van NFS, extra mappen montages).
Resultaat
Wat zullen we zien wanneer we de voorbereide configuratie toepassen?
In de merge request zal een samenvattende statistiek van de tests verschijnen die in de laatste pipeline zijn uitgevoerd:

Op elke fout kan hierop geklikt worden om details te ontvangen:

NB: Een oplettende lezer zal opmerken dat we een NodeJS-applicatie testen, terwijl op de screenshots .NET staat… Verwonder je niet: gewoon in het kader van het voorbereiden van het artikel waren er geen fouten in de tests van de eerste applicatie, maar er werden fouten in de andere gevonden.
Conclusie
Zoals je ziet, niets moeilijks!
In principe, als je al een shell-builder hebt die werkt, en Kubernetes heb je niet nodig — het integreren van testen zal een nog eenvoudigere taak zijn dan hier beschreven. En in de vind je voorbeelden voor Ruby, Go, Gradle, Maven, en enkele anderen.
P.S.
Lees ook op onze blog:
- «»;
- «»;
- «».
Bron: habr.com
