Malgré le fait que tout le monde sait parfaitement que tester votre logiciel est important et nécessaire, et que beaucoup le font automatiquement depuis longtemps, dans l'immensité de Habr, il n'y avait pas de recette unique pour mettre en place une combinaison de produits aussi populaires dans ce créneau comme (notre préféré) GitLab et JUnit . Comblons cette lacune !
Introduction
Tout d'abord, permettez-moi de donner un peu de contexte :
Puisque toutes nos applications fonctionnent sur Kubernetes, nous envisagerons d'effectuer des tests sur l'infrastructure appropriée.
Pour l'assemblage et le déploiement, nous utilisons cour (en termes de composants d'infrastructure, cela signifie aussi automatiquement que Helm est impliqué).
Je n'entrerai pas dans les détails de la création proprement dite des tests : dans notre cas, le client écrit lui-même les tests, et nous assurons uniquement leur lancement (et la présence d'un rapport correspondant dans la demande de fusion).
À quoi ressemblera la séquence générale des actions ?
Construire l'application - nous omettrons la description de cette étape.
Déployez l'application sur un espace de noms distinct du cluster Kubernetes et démarrez les tests.
Recherche d'artefacts et analyse des rapports JUnit avec GitLab.
Suppression d'un espace de noms précédemment créé.
Maintenant, place à la mise en œuvre !
réglage
CI GitLab
Commençons par un fragment .gitlab-ci.yaml, qui décrit le déploiement de l'application et l'exécution des tests. La liste s'est avérée assez volumineuse, elle a donc été soigneusement complétée par des commentaires :
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
Maintenant dans le répertoire .helm/templates créons YAML avec Job - tests-job.yaml — pour exécuter les tests et les ressources Kubernetes dont il a besoin. Voir les explications après l'inscription :
Quel genre de ressources décrit dans cette configuration ? Lors du déploiement, nous créons un espace de noms unique pour le projet (ceci est indiqué dans .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) et déployez-le :
Carte de configuration avec script de test ;
Emploi avec une description du pod et la directive spécifiée command, qui exécute simplement les tests ;
PV et PVC, qui vous permettent de stocker les données de test.
Faites attention à la condition d'introduction avec if au début du manifeste - par conséquent, les autres fichiers YAML de la charte Helm avec l'application doivent être enveloppés dans sens inverse concevoir de manière à ce qu’ils ne soient pas déployés pendant les tests. C'est-à-dire:
{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}
Cependant, si les tests nécessitent une certaine infrastructure (par exemple, Redis, RabbitMQ, Mongo, PostgreSQL...) - leurs YAML peuvent être aucun éteindre. Déployez-les également dans un environnement de test... en les ajustant comme bon vous semble, bien sûr.
Touche finale
Parce que l'assemblage et le déploiement à l'aide de Werf fonctionnent pour l'instant seulement sur le serveur de build (avec gitlab-runner), et que le pod avec les tests est lancé sur le master, vous devrez créer un répertoire /mnt/tests sur le maître et donnez-le au coureur, par exemple, via NFS. Un exemple détaillé avec des explications peut être trouvé dans Documentation K8.
Personne n'interdit de créer un partage NFS directement sur gitlab-runner, puis de le monter dans des pods.
Noter
Vous vous demandez peut-être pourquoi tout compliquer en créant un Job si vous pouvez simplement exécuter un script avec des tests directement sur le shell runner ? La réponse est assez triviale...
Certains tests nécessitent un accès à l'infrastructure (MongoDB, RabbitMQ, PostgreSQL, etc.) pour vérifier leur bon fonctionnement. Nous rendons les tests unifiés - avec cette approche, il devient facile d'inclure de telles entités supplémentaires. En plus de cela, nous obtenons standard approche de déploiement (même si vous utilisez NFS, montage supplémentaire de répertoires).
Résultat
Que verrons-nous lorsque nous appliquerons la configuration préparée ?
La demande de fusion affichera des statistiques récapitulatives pour les tests exécutés dans son dernier pipeline :
Chaque erreur peut être cliqué ici pour plus de détails :
NB: Le lecteur attentif remarquera que nous testons une application NodeJS, et dans les captures d'écran - .NET... Ne soyez pas surpris : c'est juste qu'en préparant l'article, aucune erreur n'a été trouvée lors du test de la première application, mais elles ont été retrouvés dans un autre.
Conclusion
Comme vous pouvez le constater, rien de compliqué !
En principe, si vous disposez déjà d'un collecteur de shell et qu'il fonctionne, mais que vous n'avez pas besoin de Kubernetes, y attacher des tests sera une tâche encore plus simple que celle décrite ici. Et en Documentation GitLabCI vous trouverez des exemples pour Ruby, Go, Gradle, Maven et quelques autres.