Embora todos saibam que testar seu software é importante e necessário, e muitos já fazem isso automaticamente, não encontramos nenhuma receita no Habra para configurar a combinação de produtos tão populares nesta área, como (nossa favorita) GitLab e JUnit. Vamos preencher essa lacuna!

Introdução
Para começar, vou definir o contexto:
- Como todos os nossos aplicativos funcionam no Kubernetes, será abordada a execução de testes na infraestrutura correspondente.
- Para construção e implantação, utilizamos (no sentido de componentes de infraestrutura, isso também significa automaticamente que o Helm está envolvido).
- Não vou entrar em detalhes sobre a criação direta dos testes: no nosso caso, o cliente escreve os testes por conta própria, e nós apenas garantimos sua execução (e a presença do relatório correspondente na solicitação de merge).
Como será a sequência geral de ações?
- Construção do aplicativo — descreveremos esta etapa, mas a omitiremos.
- Implantação do aplicativo em um namespace separado do cluster Kubernetes e início da execução dos testes.
- Busca de artefatos e parsing do relatório JUnit pelo GitLab.
- Remoção do namespace criado anteriormente.
Agora — vamos à implementação!
Configuração
GitLab CI
Começaremos com o fragmento .gitlab-ci.yaml, que descreve a implantação do aplicativo e a execução dos testes. A listagem acabou ficando bastante volumosa, portanto foi suficientemente complementada com comentários:
variáveis:
# declaramos a versão do werf que pretendemos usar
WERF_VERSION: "1.0 beta"
.base_deploy: &base_deploy
script:
# criamos o namespace no K8s, se não existir
- kubectl --context="${WERF_KUBE_CONTEXT}" get ns ${CI_ENVIRONMENT_SLUG} || kubectl create ns ${CI_ENVIRONMENT_SLUG}
# carregamos o werf e implantamos — mais detalhes estão na documentação
# (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:-''}"
# passamos a variável `run_tests`
# ela será usada na renderização do Helm release
--set "global.run_tests=${RUN_TESTS:-no}"
--set "global.env=${CI_ENVIRONMENT_SLUG}"
# alteramos o timeout (podem haver testes longos) e o passamos na release
--set "global.ci_timeout=${CI_TIMEOUT:-900}"
--timeout ${CI_TIMEOUT:-900}
dependencies:
- Build
.test-base: &test-base
extends: .base_deploy
before_script:
# criamos um diretório para o futuro relatório, com base no $CI_COMMIT_REF_SLUG
- mkdir /mnt/tests/${CI_COMMIT_REF_SLUG} || true
# solução alternativa forçada, pois o GitLab espera que os artefatos estejam em seu diretório build-dir
- mkdir ./tests || true
- ln -s /mnt/tests/${CI_COMMIT_REF_SLUG} ./tests/${CI_COMMIT_REF_SLUG}
after_script:
# após a conclusão dos testes, removemos a release junto com o Job
# (e, possivelmente, sua infraestrutura)
- 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
# permitimos falhas, mas você pode optar por diferente
allow_failure: true
variables:
RUN_TESTS: 'yes'
# definimos o contexto no werf
# (https://werf.io/how_to/gitlab_ci_cd_integration.html#infrastructure)
WERF_KUBE_CONTEXT: 'admin@stage-cluster'
tags:
# usamos um runner com o tag `werf-runner`
- werf-runner
artifacts:
# é necessário coletar um artefato para que possa ser visto
# no pipeline e baixado — por exemplo, para uma análise mais profunda
paths:
- ./tests/${CI_COMMIT_REF_SLUG}/*
# artefatos com mais de uma semana serão removidos
expire_in: 7 days
# importante: essas linhas são responsáveis pela análise do relatório pelo GitLab
reports:
junit: ./tests/${CI_COMMIT_REF_SLUG}/report.xml
# por simplificação, aqui estão mostradas apenas duas etapas
# na realidade você terá mais — principalmente devido à implantação
stages:
- build
- tests
build:
stage: build
script:
# construção — novamente, de acordo com a documentação do 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:
# "a essência" da nomeação do namespace
# (https://docs.gitlab.com/ce/ci/variables/predefined_variables.html)
name: tests-${CI_COMMIT_REF_SLUG}
stage: tests
except:
- schedulesKubernetes
Agora na diretório .helm/templates vamos criar um YAML com Job — tests-job.yaml — para executar os testes e os recursos necessários do Kubernetes. Para explicações, veja após a listagem:
{{- 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 }} Quais recursos estão descritos nesta configuração? Ao implantar, criamos um namespace único para o projeto (isso é indicado ainda em .gitlab-ci.yaml — tests-${CI_COMMIT_REF_SLUG}) e implantamos nele:
- ConfigMap com o script de teste;
- Trabalho com a descrição do pod e a diretiva especificada
command, que inicia os testes; - PV e PVC, que permitem armazenar os dados dos testes.
Note a condição introdutória com if no início do manifesto — portanto, outros arquivos YAML do Helm chart com o aplicativo devem ser envolvidos em uma construção oposta, para que não sejam implantados durante os testes. Ou seja:
{{- if ne .Values.global.run_tests "yes" }}
---
outro yaml
{{- end }}No entanto, se os testes exigirem alguma infraestrutura (como Redis, RabbitMQ, Mongo, PostgreSQL...) — seus YAMLs podem ser não desligar. Expanda-os e utilize em um ambiente de teste… claro, ajustando conforme sua preferência.
Toque Final
Como a montagem e o deploy usando werf atualmente funcionam apenas no servidor de build (com gitlab-runner), e o pod com os testes é iniciado no mestre, será necessário criar um diretório /mnt/tests no mestre e disponibilizá-lo para o runner, por exemplo, via NFS. Um exemplo expandido com explicações pode ser encontrado na .
O resultado será:
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 0Nada impede que você também crie um compartilhamento NFS diretamente no gitlab-runner, depois montando-o nos pods.
Nota
Pode ser que você se pergunte por que complicar tudo criando um Job, quando se pode simplesmente executar um script de testes diretamente no runner shell? A resposta é bastante trivial…
Alguns testes exigem acesso à infraestrutura (MongoDB, RabbitMQ, PostgreSQL, etc.) para verificar a integridade do funcionamento com eles. Estamos tornando os testes padronizados — com essa abordagem, incluir tais entidades adicionais torna-se fácil. Além disso, obtemos o nível padrão uma abordagem para o deploy (mesmo que utilizando NFS e montagens adicionais de diretórios).
Resultado
O que veremos quando aplicarmos a configuração preparada?
No merge request, será mostrada uma estatística resumida dos testes executados em seu último pipeline:

Para cada erro, é possível clicar e obter detalhes:

NB: O leitor atento notará que estamos testando um aplicativo NodeJS, mas nas capturas de tela aparece .NET… Não se surpreenda: durante a preparação do artigo, não foram encontrados erros na testagem do primeiro aplicativo, mas erros foram encontrados no segundo.
Conclusão
Como pode ser visto, nada complicado!
Na verdade, se você já tem um builder shell que está funcionando, e não precisa do Kubernetes — integrar testes a ele será uma tarefa ainda mais simples do que a descrita aqui. E na você encontrará exemplos para Ruby, Go, Gradle, Maven e alguns outros.
P.S.
Confira também em nosso blog:
- «»;
- «»;
- «».
Fonte: habr.com
