Yazılımınızı test etmenin önemli ve gerekli olduğunu herkesin çok iyi bilmesine ve birçoğunun bunu uzun süredir otomatik olarak yapmasına rağmen, Habr'ın geniş topraklarında bu kadar popüler ürünlerin bir kombinasyonunu oluşturmak için tek bir tarif yoktu. Bu niş (en sevdiğimiz) GitLab ve JUnit olarak. Bu boşluğu dolduralım!

Giriş
Öncelikle biraz bağlam aktarayım:
- Tüm uygulamalarımız Kubernetes üzerinde çalıştığından testleri uygun altyapı üzerinde çalıştırmayı değerlendireceğiz.
- Montaj ve dağıtım için kullanıyoruz (altyapı bileşenleri açısından bu aynı zamanda otomatik olarak Helm'in de dahil olduğu anlamına gelir).
- Gerçek test oluşturma ayrıntılarına girmeyeceğim: bizim durumumuzda müşteri testleri kendisi yazar ve biz yalnızca bunların başlatılmasını (ve birleştirme talebinde ilgili bir raporun varlığını) sağlarız.
Genel eylem sırası nasıl görünecek?
- Uygulamanın oluşturulması - bu aşamanın açıklamasını atlayacağız.
- Uygulamayı Kubernetes kümesinin ayrı bir ad alanına dağıtın ve test etmeye başlayın.
- Yapıları arama ve JUnit raporlarını GitLab ile ayrıştırma.
- Daha önce oluşturulmuş bir ad alanını silme.
Şimdi - uygulamaya!
Ayar
GitLab CI
Bir parçayla başlayalım .gitlab-ci.yaml, uygulamanın dağıtılmasını ve testlerin çalıştırılmasını açıklar. Listenin oldukça hacimli olduğu ortaya çıktı, bu nedenle yorumlarla tamamen desteklendi:
variables:
# объявляем версию werf, которую собираемся использовать
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# создаем namespace в K8s, если его нет
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# загружаем werf и деплоим — подробнее об этом см. в документации
# (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:-''}"
# передаем переменную `run_tests`
# она будет использоваться в рендере Helm-релиза
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# изменяем timeout (бывают долгие тесты) и передаем его в релиз
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# создаем директорию для будущего отчета, исходя из $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# вынужденный костыль, т.к. GitLab хочет получить артефакты в своем build-dir’е
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# после окончания тестов удаляем релиз вместе с Job’ом
# (и, возможно, его инфраструктурой)
- 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
# мы разрешаем падения, но вы можете сделать иначе
allow_failure: true
variables:
RUN_TESTS: 'yes'
# задаем контекст в werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# используем раннер с тегом `werf-runner`
- werf-runner
artifacts:
# требуется собрать артефакт для того, чтобы его можно было увидеть
# в пайплайне и скачать — например, для более вдумчивого изучения
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# артефакты старше недели будут удалены
expire_in: 7 day
# важно: эти строки отвечают за парсинг отчета GitLab’ом
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# для упрощения здесь показаны всего две стадии
# в реальности же у вас их будет больше — как минимум из-за деплоя
stages:
- build
- tests
build:
stage: build
script:
# сборка — снова по документации по 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:
# "сама соль" именования namespace’а
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedules
Kubernetes
Şimdi dizinde .helm/templates hadi Job ile YAML oluşturalım - tests-job.yaml — testleri ve ihtiyaç duyduğu Kubernetes kaynaklarını çalıştırmak için. Listelemeden sonra açıklamalara bakın:
{{- 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 }}
Ne tür kaynaklar Bu konfigürasyonda tanımlandı mı? Dağıtım sırasında proje için benzersiz bir ad alanı oluştururuz (bu, .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) ve yuvarlayın:
- Yapılandırma haritası test komut dosyasıyla;
- İş bölmenin açıklaması ve belirtilen direktifle birlikte
commandsadece testleri çalıştıran; - PV ve PVCtest verilerini saklamanıza olanak tanır.
Giriş durumuna dikkat edin if bildirimin başında - buna göre, Helm grafiğinin uygulamayla birlikte diğer YAML dosyaları sarılmalıdır обратную test sırasında konuşlandırılmayacak şekilde tasarlayın. Yani:
{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}
Ancak eğer testler biraz altyapı gerektiriyor (örneğin, Redis, RabbitMQ, Mongo, PostgreSQL...) - YAML'leri hayır kapamak. Bunları bir test ortamına da dağıtın... Elbette uygun gördüğünüz şekilde ayarlayın.
Son dokunuş
Çünkü werf kullanarak montaj ve dağıtım şimdilik çalışıyor sadece derleme sunucusunda (gitlab-runner ile) ve testlerin bulunduğu bölme ana bilgisayarda başlatıldığında, bir dizin oluşturmanız gerekecektir. /mnt/tests ustaya ve koşucuya ver, örneğin, NFS yoluyla. Açıklamalarla birlikte ayrıntılı bir örneği şu adreste bulabilirsiniz: .
Sonuç şöyle olacaktır:
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 0
Hiç kimse doğrudan gitlab-runner'da bir NFS paylaşımı yapmayı ve ardından bunu bölmelere monte etmeyi yasaklamaz.
Dikkat
Doğrudan kabuk çalıştırıcısında testlerle bir komut dosyası çalıştırabiliyorsanız, bir İş oluşturarak neden her şeyi karmaşık hale getirdiğinizi soruyor olabilirsiniz? Cevap oldukça önemsiz...
Bazı testlerin doğru çalıştığını doğrulamak için altyapıya (MongoDB, RabbitMQ, PostgreSQL vb.) erişim gerekir. Testleri birleştirilmiş hale getiriyoruz; bu yaklaşımla bu tür ek birimlerin dahil edilmesi kolaylaşıyor. Buna ek olarak şunu alıyoruz: standart dağıtım yaklaşımı (NFS kullanılıyor olsa bile, dizinlerin ek montajı).
sonuç
Hazırlanan konfigürasyonu uyguladığımızda ne göreceğiz?
Birleştirme isteği, en son ardışık düzende çalıştırılan testlere ilişkin özet istatistikleri gösterecektir:

Ayrıntılar için her hataya buraya tıklanabilir:

NB: Dikkatli okuyucu, bir NodeJS uygulamasını test ettiğimizi ve ekran görüntülerinde - .NET... Şaşırmayın: makaleyi hazırlarken ilk uygulamanın testinde hiçbir hata bulunamadı, ancak onlar başka birinde bulundu.
Sonuç
Gördüğünüz gibi karmaşık bir şey yok!
Prensip olarak, zaten bir kabuk toplayıcınız varsa ve çalışıyorsa ancak Kubernetes'e ihtiyacınız yoksa, buna test eklemek burada açıklanandan daha basit bir görev olacaktır. Ve Ruby, Go, Gradle, Maven ve diğerleri için örnekler bulacaksınız.
PS
Blogumuzda da okuyun:
- «";
- «";
- «'.
Kaynak: habr.com
