Entrega Contínua: Análise de código, testes, build e deploy automatizado com CircleCI & gradle
Tags: tutorial, programming, devops, braziliandevs
Published: 2020-09-29 12:46:59

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](https://pt.wikipedia.org/wiki/Entrega_cont%C3%ADnua)

### 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](https://circleci.com/github-enterprise-and-circleci/?utm_source=gb&utm_medium=SEM&utm_campaign=SEM-gb-Ld-latam&utm_content=SEM-gb-Ld-latam-CircleCI&utm_term=GHEntLP&gclid=CjwKCAjw8MD7BRArEiwAGZsrBWSuY43Yk4e5_y36jey6VN9bG2tOtFlkwTGW91BjxqhnKehji3UoVBoCEsMQAvD_BwE), 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](https://gradle.org/) 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: 

```kotlin

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:

```kotlin
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](https://circleci.com/docs/2.0/orb-intro) 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](www.heroku.com) 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:


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d5yep7zaa3f0old7n8ui.png)

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:

```kotlin

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 :grinning: