Integração Contínua: Configurar Projeto Java

Configuraremos um projeto Java EE 8 com Spring Boot, o Spring Boot é rápido e fácil de configurar, fazendo assim com que foquemos na criação dos jobs do Jenkins, e não percamos muito tempo na arquitetura do projeto, pois o foco do artigo é a integração contínua e não o projeto Java.

Arquitetura Java

Vamos montar um arquitetura Java EE utilizando:

  • Oracle Java JDK 1.8.0_45;
  • Gradle 2.4: Build do projeto;
  • Spring Boot: Configuração da Servlet para execução da página HTML e criação das APIs REST;
  • Spring Boot: Configuração do Spring JPA para acesso ao banco de dados;
  • Liquibase: Para execução dos scripts, e versionamento do banco de dados;
  • JUnit com Mockito: Execução dos testes unitários, integrados e de aceitação;
  • Selenium com PhantomJS: Criação de testes de aceitação.

Arquitetura HTML

  • Bootstrap;
  • JQuery.

Jenkins

Jenkins na versão 1.609.1, instalado em ambiente Linux CentOS 7, virtualizado utilizando Vagrant, com os seguintes plugins (os plugins padrões do Jenkins não serão citados):

  • Copy Artifacts;
  • Build Monitor;
  • Build Pipeline;
  • Git;
  • Gradle Plugin.

Infraestrutura

  • MySQL Server 5;
  • Nexus;
  • Servidor Apache Tomcat 8.

Preparando Gradle do Projeto Java

Vamos começar montando o projeto Java EE, para posteriormente executaremos a integração contínua sobre ele. Para isso preparemos o arquivo de build do Gradle com todas as configurações necessárias para o projeto. Logo abaixo explicaremos os principais pontos de configuração que influenciaram na execução da integração contínua.

ps: O artigo foi utilizando Gradle, no final do artigo colocaremos um link do site oficial do Spring para quem tiver interesse em fazer essa configuração com o Maven.

Vejam as configurações na Listagem 1.

Listagem 1. Configurando nosso projeto no build.gradle

group '$groupId'
version '$projectVersion'
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'war'
apply plugin: 'spring-boot'
apply plugin: 'org.liquibase.gradle'
apply plugin: 'com.bmuschko.cargo'
/** Compilação do Java **/
sourceCompatibility = 1.8
targetCompatibility = 1.8
ext {
    // dados do projeto
    projectVersion = '1.0-SNAPSHOT'
    groupId = 'com.wordpress.fabiohbarbosa'
    artifactId = 'ci'
    packaging = 'war'
    // nexus
    nexusSnapshotRepository = 'http://10.0.0.100:8081/nexus/content/repositories/snapshots'
    nexusUserName = 'deployment'
    nexusPassword = 'deployment123'
    // tomcat
    tomcatVersion = 'tomcat8x'
    tomcatPort = 8080
    tomcatHostname = '10.0.0.100'
    tomcatUsername = 'deployment'
    tomcatPassword = 'deployment123'
    // liquibase
    liquibaseChangelog = 'src/main/resources/db.changelog-master.yaml'
    liquibaseUrl = 'jdbc:mysql://10.0.0.100:3306/ci'
    liquibaseUsername = 'root'
    liquibasePassword = 'admin'
}
/** Scripts adicionados ao build **/
buildscript {
    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath(
                'org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE',
                'mysql:mysql-connector-java:5.1.36',
                'org.yaml:snakeyaml:1.15',
                'org.liquibase:liquibase-gradle-plugin:1.1.0',
                'com.bmuschko:gradle-cargo-plugin:2.1.1'
        )
    }
}
/** Liquibase **/
liquibase {
    activities {
        main {
            changeLogFile project.liquibaseChangelog
            url project.liquibaseUrl
            username project.liquibaseUsername
            password project.liquibasePassword
        }
        runList = 'main'
    }
}
/** Testes **/
test {
    exclude 'com/wordpress/fabiohbarbosa/ci/**/**IT.class'
    exclude 'com/wordpress/fabiohbarbosa/ci/**/**HTML.class'
}
task integrationTest(type: Test) {
    include 'com/wordpress/fabiohbarbosa/ci/**/**IT.class'
    testClassesDir = sourceSets.test.output.classesDir
    classpath = sourceSets.test.runtimeClasspath
    reports.html.destination = file("$buildDir/reports/integration-tests")
}
task acceptanceTest(type: Test) {
    include 'com/wordpress/fabiohbarbosa/ci/**/**HTML.class'
    testClassesDir = sourceSets.test.output.classesDir
    classpath = sourceSets.test.runtimeClasspath
    reports.html.destination = file("$buildDir/reports/acceptance-tests")
}
/** Dependências **/
repositories {
    jcenter()
    mavenLocal()
    mavenCentral()
}
dependencies {
    compile(
            'org.springframework.boot:spring-boot-starter-web:1.2.5.RELEASE',
            'org.springframework.boot:spring-boot-starter-data-jpa:1.2.5.RELEASE',
            'mysql:mysql-connector-java:5.1.36'
    )
    testCompile(
            'org.springframework.boot:spring-boot-starter-test:1.2.5.RELEASE',
            'net.anthavio:phanbedder-1.9.7:1.0.0',
            'com.github.detro.ghostdriver:phantomjsdriver:1.1.0'
    )
}
/** Nexus **/
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: project.nexusSnapshotRepository) {
                authentication(userName: project.nexusUserName, password: project.nexusPassword)
            }
            pom.version = project.projectVersion
            pom.artifactId = project.artifactId
            pom.groupId = project.groupId
            pom.packaging = project.packaging
        }
    }
}
/** Apache Deploy **/
cargo {
    containerId = project.tomcatVersion
    port = project.tomcatPort
    deployable {
        context = 'ci'
    }
    remote {
        hostname = project.tomcatHostname
        username = project.tomcatUsername
        password = project.tomcatPassword
    }
}
cargoDeployRemote.dependsOn(war)
cargoRedeployRemote.dependsOn(war)

Buildscript

Com a adição do plugin do Spring Boot para Gradle (org.springframework.boot:spring-boot-gradle-plugin) é possível subir a aplicação utilizando uma task do Gradle, existe a mesma possibilidade para um goal do Maven. Isso é feito pelo Spring Boot através de um Tomcat embarcado, com o Spring Boot diversas configurações já estão prontas e são totalmente customizadas com anotações e arquivos de propriedades, sejam esses arquivos de propriedades .properties ou .yaml. No artigo utilizaremos YAML.

Foi adicionado o plugin do Liquibase (org.liquibase:liquibase-gradle-plugin) para que possamos usar as tasks do Liquibase posteriormente, o plugin tem dependência do driver connector do MySQL (mysql:mysql-connector-java) e o Snake YAML (org.yaml:snakeyaml) para o parse do arquivo de configuração do Liquibase que será feito em .yaml,

Foi adicionado também o plugin do Cargo (com.bmuschko:gradle-cargo-plugin) que usaremos para efetuar o hot deploy no Tomcat através de um dos jobs do Jenkins.

Vejam as configurações na Listagem 2.

Listagem 2. Configurando os plugins no build do projeto

buildscript {
    repositories {
        jcenter()
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath(
                'org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE',
                'mysql:mysql-connector-java:5.1.36',
                'org.yaml:snakeyaml:1.15',
                'org.liquibase:liquibase-gradle-plugin:1.1.0',
                'com.bmuschko:gradle-cargo-plugin:2.1.1'
        )
    }
}

Configurando o Plugin do Liquibase

Configuramos o plugin do Liquibase informando seu arquivo de configuração, arquivo no qual informaremos quais scripts serão executados, e quais são os dados de acesso ao banco de dados. Note que estamos usando as propriedades que foram criadas para o projeto em ext {}.

Vejam as configurações na Listagem 3.

Listagem 3. Configuração do plugin do Liquibase no Gradle

liquibase {
    activities {
        main {
            changeLogFile project.liquibaseChangelog
            url project.liquibaseUrl
            username project.liquibaseUsername
            password project.liquibasePassword
        }
        runList = 'main'
    }
}

Isolando Testes de Integração e Aceitação

Devemos isolar a execução dos testes unitários, integrados e de aceitação, pois no Jenkins criaremos um job para execução de testes, para isso excluímos os arquivos compilados com sufixo IT.class da execução dos testes unitários e customizamos duas novas tasks do Gradle para a execução dos integrados e de aceitação.

No caso do Maven separe essa execução customizando o surefire para executar os unitários e o failsafe os integrados (ou vice e versa). Nunca tentei separar em três execuções acredito não ser possível nesse caso aconselho a juntar os integrados e aceitação.

Vejam as configurações na Listagem 4.

Listagem 4. Customizando a execução dos testes

test {
    exclude 'com/wordpress/fabiohbarbosa/ci/**/**IT.class'
    exclude 'com/wordpress/fabiohbarbosa/ci/**/**HTML.class'
}
task integrationTest(type: Test) {
    include 'com/wordpress/fabiohbarbosa/ci/**/**IT.class'
    testClassesDir = sourceSets.test.output.classesDir
    classpath = sourceSets.test.runtimeClasspath
    reports.html.destination = file("$buildDir/reports/integration-tests")
}
task acceptanceTest(type: Test) {
    include 'com/wordpress/fabiohbarbosa/ci/**/**HTML.class'
    testClassesDir = sourceSets.test.output.classesDir
    classpath = sourceSets.test.runtimeClasspath
    reports.html.destination = file("$buildDir/reports/acceptance-tests")
}

Dependências do Projeto

Aqui destacamos as três dependências do Spring Boot, a primeira Spring Boot Web (org.springframework.boot:spring-boot-starter-web) que vai disponibilizar a Servlet para nosso projeto RESTFul. O Spring Boot JPA (org.springframework.boot:spring-boot-starter-data-jpa) para acesso ao banco de dados e o Spring Boot Test (org.springframework.boot:spring-boot-starter-test) que usaremos para subir o servidor para os testes integrados e de aceitação.

Também adicionamos as dependências do MySQL connector (mysql:mysql-connector-java:5.1.36) para o funcionamento do Spring JPA e o PhantomJS + Selenium (net.anthavio:phanbedder-1.9.7:1.0.0 e com.github.detro.ghostdriver:phantomjsdriver:1.1.0) para os testes de aceitação.

Vejam as configurações na Listagem 5.

Listagem 5. Dependências do projeto

repositories {
    jcenter()
    mavenLocal()
    mavenCentral()
}
dependencies {
    compile(
            'org.springframework.boot:spring-boot-starter-web:1.2.5.RELEASE',
            'org.springframework.boot:spring-boot-starter-data-jpa:1.2.5.RELEASE',
            'mysql:mysql-connector-java:5.1.36'
    )
    testCompile(
            'org.springframework.boot:spring-boot-starter-test:1.2.5.RELEASE',
            'net.anthavio:phanbedder-1.9.7:1.0.0',
            'com.github.detro.ghostdriver:phantomjsdriver:1.1.0'
    )
}

Nexus

A cada build da versão enviaremos para o Nexus o artefato (WAR), para que possamos ter todo o histórico de versões geradas, isso é bacana pois ajuda a resgatar versões antigas rapidamente. O Nexus está preparado para guardar artefatos do Maven, porém de uma forma fácil é possível configurar no Gradle as variáveis necessárias do Maven para envio do artefato ao Nexus. No inicio do arquivo gradle.build adicionamos o plugin war, e em em uploadArchives configuraremos o acesso ao Nexus.

Vejam as configurações na Listagem 6.

Listagem 6. Configuração de acesso ao Nexus

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: project.nexusSnapshotRepository) {
                authentication(userName: project.nexusUserName, password: project.nexusPassword)
            }
            pom.version = project.projectVersion
            pom.artifactId = project.artifactId
            pom.groupId = project.groupId
            pom.packaging = project.packaging
        }
    }
}

Hot Deploy no Tomcat

Após adicionar o plugin Cargo devemos configurar o acesso ao Apache Tomcat, em seguida adicionamos as tasks cargoDeployRemote e cargoRedeployRemote a task WAR do Gradle, pois sem o WAR gerado não é possível efetuar o hot deploy.

Um ponto interessante a ser destacado é que o usuário que será utilizado aqui deve ter a role manager-script configurada no Apache Tomcat, sem isso não é possível efetuar o deploy externo. Normalmente essa configuração pode ser feita no arquivo conf/tomcat-users.xml.

Vejam as configurações na Listagem 7.

Listagem 7. Configuração do Cargo para o hot deploy no Apache Tomcat

cargo {
    containerId = project.tomcatVersion
    port = project.tomcatPort
    deployable {
        context = 'ci'
    }
    remote {
        hostname = project.tomcatHostname
        username = project.tomcatUsername
        password = project.tomcatPassword
    }
}
cargoDeployRemote.dependsOn(war)
cargoRedeployRemote.dependsOn(war)

No próximo tópico iremos configurar os scripts e acesso ao banco de dados.

Você também pode visualizar o repositório no github.

Anúncios
Marcado com: , , , , , , , , , , , , , , , , , , , , , , , , ,
Publicado em Desenvolvimento de Software, DevOps, Git, Integração Contínua, Java, Selenium

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: