Въпреки факта, че всеки знае отлично, че тестването на вашия софтуер е важно и необходимо и мнозина го правят автоматично от дълго време, в необятността на Habr нямаше нито една рецепта за създаване на комбинация от толкова популярни продукти в тази ниша като (любимите ни) GitLab и JUnit. Нека запълним тази празнина!
Уводна
Първо, нека дам малко контекст:
Тъй като всички наши приложения работят на Kubernetes, ще обмислим провеждането на тестове на подходящата инфраструктура.
За сглобяване и разгръщане използваме werf (по отношение на инфраструктурните компоненти, това също автоматично означава, че Helm е включен).
Няма да навлизам в подробности за действителното създаване на тестове: в нашия случай клиентът сам пише тестовете и ние осигуряваме само тяхното стартиране (и наличието на съответен отчет в заявката за сливане).
Как ще изглежда общата последователност от действия?
Изграждане на приложението – ще пропуснем описанието на този етап.
Разположете приложението в отделно пространство от имена на клъстера Kubernetes и започнете да тествате.
Търсене на артефакти и анализиране на JUnit отчети с GitLab.
Изтриване на предварително създадено пространство от имена.
Сега - към изпълнение!
регулиране
GitLab CI
Да започнем с един фрагмент .gitlab-ci.yaml, който описва внедряването на приложението и провеждането на тестове. Списъкът се оказа доста обемен, така че беше старателно допълнен с коментари:
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
Сега в директорията .helm/templates нека създадем YAML с Job - tests-job.yaml — за провеждане на тестове и необходимите ресурси на Kubernetes. Вижте обясненията след изброяването:
Какъв вид ресурси описани в тази конфигурация? При внедряването ние създаваме уникално пространство от имена за проекта (това е посочено в .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) и го разточете:
ConfigMap с тестов скрипт;
Работа с описание на подс и посочената директива command, който само изпълнява тестовете;
PV и PVC, които ви позволяват да съхранявате тестови данни.
Обърнете внимание на уводното условие с if в началото на манифеста - съответно другите YAML файлове на диаграмата на Helm с приложението трябва да бъдат обвити в обратен проектирайте така, че да не се разгръщат по време на тестване. Това е:
{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}
Въпреки това, ако тестовете изискват известна инфраструктура (например Redis, RabbitMQ, Mongo, PostgreSQL...) - техните YAML могат да бъдат не изключи. Разположете ги и в тестова среда... като ги коригирате както сметнете за добре, разбира се.
Последно докосване
защото асемблирането и внедряването с помощта на werf работи засега само на сървъра за изграждане (с gitlab-runner) и подът с тестове се стартира на главния, ще трябва да създадете директория /mnt/tests върху майстора и го дай на бегача, например чрез NFS. Подробен пример с обяснения можете да намерите в K8s документация.
Никой не забранява да правите споделяне на NFS директно на gitlab-runner и след това да го монтирате в pods.
Внимание
Може би се питате защо да усложнявате всичко, като създавате Job, ако можете просто да стартирате скрипт с тестове директно в shell runner-а? Отговорът е доста тривиален...
Някои тестове изискват достъп до инфраструктурата (MongoDB, RabbitMQ, PostgreSQL и др.), за да се провери дали работят правилно. Ние правим тестването унифицирано - с този подход става лесно включването на такива допълнителни обекти. В допълнение към това получаваме стандарт подход за внедряване (дори ако използвате NFS, допълнително монтиране на директории).
Резултат
Какво ще видим, когато приложим подготвената конфигурация?
Заявката за сливане ще покаже обобщена статистика за тестове, изпълнявани в последния й конвейер:
Всяка грешка може да бъде кликната тук за подробности:
NB: Внимателният читател ще забележи, че тестваме приложение NodeJS, а на екранните снимки - .NET... Не се изненадвайте: просто при подготовката на статията не бяха открити грешки при тестването на първото приложение, но те са намерени в друг.
Заключение
Както можете да видите, нищо сложно!
По принцип, ако вече имате колектор на обвивки и той работи, но не се нуждаете от Kubernetes, прикачването на тестване към него ще бъде дори по-проста задача от описаната тук. И в GitLab CI документация ще намерите примери за Ruby, Go, Gradle, Maven и някои други.