Testes
Em um segundo stage, vamos executar testes. Podemos executar vários tipos de testes, mas vamos começar pelo primeiro, que são os testes unitários. Em desenvolvimento é boa prática gerar os testes unitários do código. Não vamos gerar uma imagem sem testar o build antes certo?
Porém podemos testar muito mais do que os testes unitários, por exemplo:
- Qualidade de código
- Issues de segurança
- Pacotes com CVEs
- Linter para o código
- etc
Pra que vamos buildar o código se já tem problema com linter? Então esse processo deveria vir antes do build para nem perder tempo. Vamos rodar o teste unitário nesse momento.
Para que executemos um teste unitário é necessário desenvolvê-lo, mas o projeto em questão já tem o teste unitário que vamos utilizar. Não vamos entrar em detalhes sobre isso, apenas mostrar como executar para o nosso job.
Testes unitários são desenvolvidos para testar um pequeno trecho isolado de código, geralmente uma função ou método. Se os valores de entrada geram a saída esperada então o teste passa.
❯ tree src
src
├── App.css
├── App.jsx
├── App.test.jsx # Esse é o teste para App.jsx
├── assets
│ ├── gitlab.svg
│ └── react.svg
├── index.css
└── main.jsx
2 directories, 7 files
#Executando teste unitário.
npm test
> [email protected] test
> vitest
DEV v3.1.2 /Users/davidprata/Desktop/gitlab/learn-gitlab-app
✓ src/App.test.jsx (3 tests) 27ms
✓ an always true assertion (1)
✓ should be equal to 2 1ms
✓ App (2)
✓ renders the App component 22ms
✓ shows the GitLab logo 3ms
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 09:26:10
Duration 920ms (transform 34ms, setup 190ms, collect 33ms, tests 27ms, environment 409ms, prepare 48ms)
JUNIT report written to /Users/davidprata/Desktop/gitlab/learn-gitlab-app/reports/junit.xml
HTML Report is generated
You can run npx vite preview --outDir reports/html to see the test results.
PASS Waiting for file changes...
press h to show help, press q to quit
Então vamos colocar isso no nosso pipeline. Rodar o comando npm test depende de alguns pacotes que são instalados e por isso é necessário rodar o npm ci antes.
default:
tags:
- general
stages:
- build
build:
stage: build
image: node:22-slim
script:
- node --version
- npm --version
- npm ci # Instalação das dependências
- npm run build # Irá gerar a pasta .build
- echo "ACCESS_TOKEN=abc123" > .env # Um teste somente para o reports
artifacts:
when: on_success # Já é o padrão
expire_in: "1 hour"
paths:
- build/ # A pasta toda sem exclusões
reports:
dotenv: .env
unit-test:
stage: test
image: node:22-slim
script:
- echo "ACCESS_TOKEN É IGUAL A $ACCESS_TOKEN" # Teste ver se recebeu a variável do job anterior.
- npm ci # Instalação das dependências
- npm test
Ao subir isso temos...

Ou seja, não podemos definir um stage stage: test se ele não esta definido dentro de stages:.
❯ git add .gitlab-ci.yml
❯ git cm "add stage test fixed"
❯ git push origin pipe/test
E temos o log.
Using docker image sha256:a7bca975c7f3a862dc60f3d8aaa3862fce3208066dd3567f060381b506f38402 for node:22-slim with digest node@sha256:157c7ea6f8c30b630d6f0d892c4f961eab9f878e88f43dd1c00514f95ceded8a ...
$ echo "ACCESS_TOKEN É IGUAL A $ACCESS_TOKEN"
ACCESS_TOKEN É IGUAL A abc123 # A variável de ambiente esta aqui.
$ npm ci
added 440 packages, and audited 441 packages in 27s
152 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
$ npm test
> [email protected] test
> vitest
RUN v3.1.2 /builds/puziol/learn-gitlab-app
✓ src/App.test.jsx > an always true assertion > should be equal to 2 3ms
✓ src/App.test.jsx > App > renders the App component 82ms
✓ src/App.test.jsx > App > shows the GitLab logo 16ms
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 12:38:02
Duration 5.05s (transform 313ms, setup 543ms, collect 168ms, tests 103ms, environment 1.85s, prepare 692ms)
JUNIT report written to /builds/puziol/learn-gitlab-app/reports/junit.xml
HTML Report is generated
You can run npx vite preview --outDir reports/html to see the test results.
Cleaning up project directory and file based variables
00:01
Job succeeded
Dica de Segurança
Tanto o reports quanto paths são injetados automaticamente no próximo job. Essa demonstração foi feita para que variáveis de ambiente necessárias em um job possam ser utilizadas em outros jobs também. Porém existe um problema grave aqui e devemos prestar atenção sempre que alguma pipeline sofrer alterações e precisarmos de um review.
script:
- node --version
- npm --version
- npm ci
- npm run build
- echo "ACCESS_TOKEN=abc123" >> .env
- echo "SECRET=$SECRET" >> .env # Estamos jogando a variável SECRET (masked) do repositório dentro do .env.
O mascaramento protege de aparecer na saída do console, mas não de aparecer nos artefatos, então fica esperto. Aqui o valor do artefato.
ACCESS_TOKEN=abc123
SECRET=platform-engineer-is-the-new-devops
Execução em Paralelo
Podemos fazer vários testes ao mesmo tempo e ganhar velocidade. Não precisamos esperar que um teste acabe para começar outro. Jobs dentro do mesmo stage executam em paralelo se não houver dependências entre eles. Falaremos de dependências mais tarde.
Temos o arquivo eslint.config.js no repositório, vamos também criar um lint com ele. Para executar isso precisamos rodar o npm run lint
O ESLint é uma ferramenta de linting para JavaScript (e outros arquivos, como JSX, TypeScript, etc.), e sua principal função é analisar o código para identificar padrões ou práticas que podem ser melhoradas, ou que possam causar erros. Ele verifica o código em busca de problemas que vão desde erros de sintaxe até práticas de codificação inconsistentes ou problemáticas. O ESLint ajuda a garantir que o código seja limpo, compreensível e livre de erros comuns.
Gosto bastante da ideia de utilizar linters para que o código não saia da formatação adequada principalmente.
default:
tags:
- general
stages: # Podereíamos remover pois os dois stages abaixo são padrões do gitlab, mas é boa prática manter.
- build
- test
build:
stage: build
image: node:22-slim
script:
- node --version
- npm --version
- npm ci
- npm run build
artifacts:
when: on_success
expire_in: "1 hour"
paths:
- build/
unit-test:
stage: test # mesmo stage
image: node:22-slim
script:
- npm ci
- npm test
lint-test:
stage: test # mesmo stage
image: node:22-slim
script:
- npm ci
- npm run lint
Só para relembrar, estamos utilizando stages padrões do GitLab (build e test), logo nem precisaríamos definir stages: [build, test]. Se omitirmos isso funcionaria normalmente.
Subindo e testando.
❯ git add .gitlab-ci.yml
❯ git commit -m "add test lint and remove reports"
❯ git push origin pipe/test

Observe que os jobs estão rodando em paralelo, pois começaram ao mesmo tempo, mas falhou o job lint-test com esse error.
$ npm run lint
> [email protected] lint
> eslint -f json -o gl-codequality.json . # Esse é o comando que esta sendo executado.
Oops! Something went wrong! :(
ESLint: 9.25.1
TypeError: Expected lint-test.artifacts.reports.codequality to be one exact path, got: undefined
at getOutputPath (/builds/puziol/learn-gitlab-app/node_modules/eslint-formatter-gitlab/index.js:51:11)
at eslintFormatterGitLab (/builds/puziol/learn-gitlab-app/node_modules/eslint-formatter-gitlab/index.js:268:54)
at Object.format (/builds/puziol/learn-gitlab-app/node_modules/eslint/lib/eslint/eslint.js:1054:12)
at printResults (/builds/puziol/learn-gitlab-app/node_modules/eslint/lib/cli.js:365:33)
at async Object.execute (/builds/puziol/learn-gitlab-app/node_modules/eslint/lib/cli.js:719:4)
at async main (/builds/puziol/learn-gitlab-app/node_modules/eslint/bin/eslint.js:160:19)
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: exit code 1
Quando executamos o npm run lint na verdade ele rodou o comando "eslint -f json -o gl-codequality.json ." e isso esta dentro de packages.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"e2e": "npx playwright test",
"lint": "eslint -f json -o gl-codequality.json .",
"preview": "vite preview"
},
}
Se alterarmos para eslint . o job irá passar. Faça isso e suba novamente. Quando falarmos melhor sobre reports voltaremos ao padrão. O que importa aqui é rodar coisas em paralelo.
Outro teste que poderíamos fazer é sobre segurança nos pacotes utilizados usando o npm audit. O snyk também uma ferramenta muito popular para procurar vulnerabilidade de pacotes e temos muitas outras.
ao utilizar o comando npm audit --json > vulnerability-report.json estamos salvando a saída para o arquivo e podemos, por enquanto, exportar como artefato. Porém isso não iria quebrar a pipeline se fosse encontrada alguma vulnerabilidade. O job estaria gerando o relatório e mais nada. Para que a saída do comando npm audit force um erro (exit 1) é necessário passar o level, para caso encontre um problema acima desse level force a saída diferente de zero (sucesso).
default:
tags:
- general
stages:
- build
- test
build:
stage: build
image: node:22-slim
script:
- node --version
- npm --version
- npm ci
- npm run build
artifacts:
when: on_success
expire_in: "1 hour"
paths:
- build/
unit-test:
stage: test # mesmo stage
image: node:22-slim
script:
- npm ci
- npm test
lint-test:
stage: test # mesmo stage
image: node:22-slim
script:
- npm ci
- npm run lint
vulnerability-test:
stage: test # mesmo stage
image: node:22-slim
script:
- npm ci
- npm audit --audit-level=high --json > vulnerability-report.json
artifacts:
when: on_failure # Só irá gerar o relatório em caso de falha.
expire_in: "1 hour"
paths:
- vulnerability-report.json
Agora temos 3 na execução em paralelo.

Esse é o tipo de caso perfeito para trabalhar com o report. Ferramentas que geram relatórios podemos trabalhar desta maneira, mas é muito mais elegante trabalhar com o reports colocando cada coisa no seu devido lugar.
artifacts:
when: on_failure # Só irá gerar o relatório em caso de falha.
expire_in: "1 hour"
paths:
- vulnerability-report.json