Entrega Contínua: Análise de código, testes, build e deploy automatizado com CircleCI & gradle
Vinícius Moraes
Posted on September 29, 2020
Nos últimos dias, venho estudando sobre entrega contínua, e, durante esse processo acabei criando um pipeline no qual eu vou compartilhar nesse post com vocês :)
Se você ainda não sabe o que é entrega contínua clique aqui
O que será abordado nesse post ?
- Análise código(bugs, segurança e vulnerabilidades) utilizando SonarQube
- Testes unitários e de Integração rodando em paralelo para otimizar o pipeline
- Build do projeto
- Deploy automático da aplicação no Heroku
- Teste de API após o deploy
A ferramenta que eu estou utilizando como CD(entrega contínua) é o CircleCI, sendo assim, é nela que eu irei mostrar os exemplos.
CircleCI
É uma plataforma de integração e entrega contínua (CICD) para sistemas operacionais como Linux, macOS e Android e etc, totalmente em nuvem.
Talk is cheap, show me the code
Nosso primeiro passo é criar as tasks no gradle para o teste unitário e teste de integração, isso nos ajudara a separar o jobs na execução do pipeline.
O código das tasks ficará assim no arquivo build.gradle.kts:
tasks {
create("unitTest", Test::class) {
filter {
includeTestsMatching("br.com.projeto.core.*")
}
testLogging {
events(TestLogEvent.PASSED)
events(TestLogEvent.FAILED)
}
}
create("integrationTest", Test::class) {
filter {
includeTestsMatching("integration.*")
}
testLogging {
events(TestLogEvent.PASSED)
events(TestLogEvent.FAILED)
}
}
}
Basicamente a task unitTest roda todos os testes que estão dentro do pacote `br.com.projeto.core.*`, já a task integrationTest como o próprio nome já diz, executa os testes de integração do pacote `integration.*`, as duas tasks tem como configuração o test logging mostrando os testes que passaram e que falharam.
No meu caso, a estrutura dos pacotes de testes do meu projeto estão assim:
test > kotlin > br.com.projeto.core (testes unitários)
test > kotlin > integration
Certo, agora que temos essas duas tasks, podemos inclui-las no nosso arquivo config.yml do CircleCI que está no projeto:
unit-test:
working_directory: ~/
docker:
- image: circleci/openjdk:11-jdk-stretch-node-browsers
steps:
- checkout
- run:
name: Unit tests
command: ./gradlew unitTest
integration-test:
working_directory: ~/
machine: true
environment:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
JAVA_TOOL_OPTIONS: "-Xmx4G"
GRADLE_OPTS: "-Xmx4G -Dorg.gradle.daemon=false -DdisablePreDex"
steps:
- run:
name: Download Java 11
command: |
sudo apt update
sudo apt install openjdk-11-jdk
- checkout
- run:
name: Integration tests
command: ./gradlew integrationTest
workflows:
'Test, Build and Deploy Workflow':
jobs:
- unit-test
- integration-test
Nosso segundo passo, é configurar o SonarQube no projeto, para que o pipeline faça o upload da análise automaticamente no SonarCloud.
SonarQube
É uma ferramente para análise de código, sendo possível analisar qualidade de código, bugs e possíveis falhas de segurança.
Dentro do arquivo build.gradle.kts é necessário inserir o código abaixo:
plugins {
id("org.sonarqube") version "3.0"
}
sonarqube {
properties {
property("sonar.projectKey", "chave do seu projeto")
property("sonar.organization", "sua organizacao")
property("sonar.host.url", "https://sonarcloud.io")
}
}
Feito isso, agora precisamos adicionar o job no pipeline, adicionando no arquivo config.yml:
Não se preocupe com o arquivo config.yml agora, vou deixar ele completo no final do post :D
code_analysis:
docker:
- image: circleci/openjdk:11-jdk-stretch-node-browsers
steps:
- checkout
- run:
name: Sonarqube analysis
command: ./gradlew sonarqube
workflows:
'Test, Build and Deploy Workflow':
jobs:
- unit-test
- integration-test
- code_analysis:
requires:
- unit-test
- integration-test
context: sonarqube
Repare que no job de code_analysis foi adicionado a tag requires que indica que esse job só deve ser executado após os jobs unit-test e integration-test terem sido executados com sucesso, a tag (context)[https://circleci.com/docs/2.0/contexts/] indica que o Job deve utilizar o context criado no CircleCI (no caso sonarqube) utilizando as variáveis que foram definidas nesse contexto.
Certo, agora que adicionamos os testes e a análise de código precisamos executar o build do projeto para que seja gerado o pacote para o deploy da aplicação.
Vamos adicionar o job que executa o build:
build:
working_directory: ~/
docker:
- image: circleci/openjdk:11-jdk-stretch-node-browsers
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "build.gradle.kts" }}
- v1-dependencies-
- run:
name: Run assemble
command: ./gradlew clean assemble
- save_cache:
paths:
- ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle.kts" }}
Agora que conseguimos fazer o build da nossa aplicação de forma automatizada, vamos para a próxima etapa, no caso o deploy.
O CircleCI disponibiliza orbs que de forma resumida, são pacotes open source que reduzem a complexidade de configuração para criar jobs, commands e executors. Vamos utilizar a orb do Heroku para executar o deploy.
version: 2.1
orbs:
heroku: circleci/heroku@1.2.2
jobs:
\* lista de jobs *\
workflows:
'Test, Build and Deploy Workflow':
jobs:
- unit-test:
- integration-test:
- code_analysis:
requires:
- unit-test
- integration-test
context: sonarqube
- build:
requires:
- unit-test
- integration-test
- heroku/deploy-via-git:
requires:
- build
filters:
branches:
only: master
Não se esqueça de configurar o seu arquivo Procfile do Heroku :) no meu caso ficou assim:
web: java -jar build/libs/projeto.jar -port=${PORT}
Com o deploy configurado, temos a última etapa que seria o teste de API após o deploy para verificar se os endpoints da aplicação continuam funcionando corretamente (nesse exemplo, temos apenas um ambiente, mas seria interessante executar os testes de API em um ambiente de desenvolvimento ou staging antes do deploy ser executado em produção).
Para essa etapa, utilizei a Orb do CircleCI chamada Postman Newman no qual lê uma collection.json de um determinado path e executa.
Como é uma orb, devemos adiciona-lá no arquivo config.yml
version: 2.1
orbs:
heroku: circleci/heroku@1.2.2
newman: postman/newman@0.0.2
jobs:
\* lista de jobs *\
Agora vamos adicionar o job correspondente ao nosso teste de API:
api-test:
working_directory: ~/
executor: newman/postman-newman-docker
steps:
- checkout
- newman/newman-run:
collection: postman/projeto-collection.json
environment: postman/projeto-environment.json
A propriedade environment contém a URL que foi gerada no Heroku durante o deploy.
No final o pipeline deve ficar dessa forma no CircleCI:
Arquivo config.yml completo:
version: 2.1
orbs:
newman: postman/newman@0.0.2
heroku: circleci/heroku@1.2.2
jobs:
unit-test:
working_directory: ~/
docker:
- image: circleci/openjdk:11-jdk-stretch-node-browsers
steps:
- checkout
- run:
name: Unit tests
command: ./gradlew unitTest
integration-test:
working_directory: ~/
machine: true
environment:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
JAVA_TOOL_OPTIONS: "-Xmx4G"
GRADLE_OPTS: "-Xmx4G -Dorg.gradle.daemon=false -DdisablePreDex"
steps:
- run:
name: Download Java 11
command: |
sudo apt update
sudo apt install openjdk-11-jdk
- checkout
- run:
name: Integration tests
command: ./gradlew integrationTest
code_analysis:
docker:
- image: circleci/openjdk:11-jdk-stretch-node-browsers
steps:
- checkout
- run:
name: Sonarqube analysis
command: ./gradlew sonarqube
build:
working_directory: ~/
machine: true
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "build.gradle.kts" }}
- v1-dependencies-
- run:
name: Run assemble
command: ./gradlew clean assemble
- save_cache:
paths:
- ~/.gradle
key: v1-dependencies-{{ checksum "build.gradle.kts" }}
api-test:
working_directory: ~/
executor: newman/postman-newman-docker
steps:
- checkout
- newman/newman-run:
collection: postman/collection.json
environment: postman/environment.json
workflows:
'Test, Build and Deploy Workflow':
jobs:
- unit-test
- integration-test
- code_analysis:
requires:
- unit-test
- integration-test
context: sonarqube
- build:
requires:
- unit-test
- integration-test
- heroku/deploy-via-git:
requires:
- build
filters:
branches:
only: master
- api-test:
requires:
- heroku/deploy-via-git
Arquivo build.gradle.kts completo:
plugins {
kotlin("jvm") version "1.3.61"
id("org.sonarqube") version "3.0"
}
sonarqube {
properties {
property("sonar.projectKey", "chave do projeto"
property("sonar.organization", "organization")
property("sonar.host.url", "https://sonarcloud.io")
}
}
tasks {
create("stage", Task::class) {
dependsOn("installDist")
}
create("unitTest", Test::class) {
filter {
includeTestsMatching("br.com.projeto.core.*")
}
testLogging {
events(TestLogEvent.PASSED)
events(TestLogEvent.FAILED)
}
}
create("integrationTest", Test::class) {
filter {
includeTestsMatching("integration.*")
}
testLogging {
events(TestLogEvent.PASSED)
events(TestLogEvent.FAILED)
}
}
}
Esse foi apenas um exemplo do que é possível fazer com o seu pipeline, existem vários outros jobs que podem ser adicionados como lint, gerar documentação, smoke testes, automatização de comandos do git, basta usar sua imaginação :)
Espero que esse post tenha te ajudado de alguma forma.
Muito obrigado por ler esse post, caso tenha ficado com alguma dúvida ou tenha alguma sugestão, sinta-se livre para deixar um comentário 😀
Posted on September 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 29, 2020