Apesar de todos saberem perfeitamente que testar seu software é importante e necessário, e muitos já o fazem de forma automática há muito tempo, na vastidão de Habr não havia uma única receita para configurar uma combinação de produtos tão populares em esse nicho como (nosso favorito) GitLab e JUnit. Vamos preencher essa lacuna!
Introdutório
Primeiro, deixe-me dar um contexto:
Como todos os nossos aplicativos são executados no Kubernetes, consideraremos a execução de testes na infraestrutura apropriada.
Para montagem e implantação usamos bem (em termos de componentes de infraestrutura, isso também significa automaticamente que o Helm está envolvido).
Não entrarei em detalhes da própria criação dos testes: no nosso caso, o próprio cliente escreve os testes, e nós apenas garantimos o seu lançamento (e a presença do relatório correspondente na solicitação de mesclagem).
Como será a sequência geral de ações?
Construindo a aplicação – omitiremos a descrição desta etapa.
Implante o aplicativo em um namespace separado do cluster Kubernetes e comece a testar.
Procurando artefatos e analisando relatórios JUnit com GitLab.
Excluindo um namespace criado anteriormente.
Agora - para implementação!
Fixação
CI do GitLab
Vamos começar com um fragmento .gitlab-ci.yaml, que descreve a implantação do aplicativo e a execução de testes. A listagem revelou-se bastante volumosa, por isso foi totalmente complementada com comentários:
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
Agora no diretório .helm/templates vamos criar YAML com Job - tests-job.yaml — para executar testes e os recursos Kubernetes necessários. Veja explicações após listagem:
Que tipo de recursos descrito nesta configuração? Ao implantar, criamos um namespace exclusivo para o projeto (isso é indicado em .gitlab-ci.yaml - tests-${CI_COMMIT_REF_SLUG}) e implemente-o:
Mapa de configuração com roteiro de teste;
Trabalho com uma descrição do pod e a diretiva especificada command, que apenas executa os testes;
Fotovoltaica e PVC, que permitem armazenar dados de teste.
Preste atenção à condição introdutória com if no início do manifesto - respectivamente, outros arquivos YAML do gráfico Helm com o aplicativo devem ser agrupados em reverter design para que eles não sejam implantados durante o teste. Aquilo é:
{{- if ne .Values.global.run_tests "yes" }}
---
я другой ямлик
{{- end }}
Contudo, se os testes requer alguma infraestrutura (por exemplo, Redis, RabbitMQ, Mongo, PostgreSQL...) - seus YAMLs podem ser não desligar. Implante-os também em um ambiente de teste... ajustando-os como achar melhor, é claro.
Toque final
Porque montagem e implantação usando werf funcionam por enquanto apenas no servidor de compilação (com gitlab-runner), e o pod com testes for iniciado no master, você precisará criar um diretório /mnt/tests no mestre e entregue ao corredor, por exemplo, via NFS. Um exemplo detalhado com explicações pode ser encontrado em Documentação K8s.
Ninguém proíbe fazer um compartilhamento NFS diretamente no gitlab-runner e depois montá-lo em pods.
Nota
Você pode estar se perguntando por que complicar tudo criando um Job se você pode simplesmente executar um script com testes diretamente no shell runner? A resposta é bastante trivial...
Alguns testes requerem acesso à infraestrutura (MongoDB, RabbitMQ, PostgreSQL, etc.) para verificar se funcionam corretamente. Tornamos os testes unificados - com essa abordagem, fica fácil incluir essas entidades adicionais. Além disso, obtemos padrão abordagem de implantação (mesmo usando NFS, montagem adicional de diretórios).
resultado
O que veremos quando aplicarmos a configuração preparada?
A solicitação de mesclagem mostrará estatísticas resumidas dos testes executados em seu pipeline mais recente:
Cada erro pode ser clicado aqui para obter detalhes:
NB: O leitor atento notará que estamos testando um aplicativo NodeJS, e nas capturas de tela - .NET... Não se surpreenda: é que durante a preparação do artigo não foram encontrados erros no teste do primeiro aplicativo, mas eles foram encontrados em outro.
Conclusão
Como você pode ver, nada complicado!
Em princípio, se você já possui um coletor de shell e ele funciona, mas não precisa do Kubernetes, anexar testes a ele será uma tarefa ainda mais simples do que a descrita aqui. E em Documentação de CI do GitLab você encontrará exemplos para Ruby, Go, Gradle, Maven e alguns outros.