Entrega Contínua: Análise de código, testes, build e deploy automatizado com CircleCI & gradle

vinigmoraes

Vinícius Moraes

Posted on September 29, 2020

Entrega Contínua: Análise de código, testes, build e deploy automatizado com CircleCI & gradle

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.*`,  a task integrationTest como o próprio nome  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.

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" }}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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 *\
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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)
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

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 😀

💖 💪 🙅 🚩
vinigmoraes
Vinícius Moraes

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