Gerando o Relatório de Cobertura de Testes Unificado com Jacoco, Robolectric e Espresso

Olá, pessoal! No post de hoje, veremos como é possível gerar um relatório de cobertura de testes em um projeto Android incluindo tanto os testes unitários (geralmente escritos com JUnit, Mockito, Robolectric, etc.) quanto os instrumentados (geralmente escritos utilizando o Espresso).

Relatórios de cobertura de testes são uma ferramenta muito importante para mensurar o quanto nossos testes realmente exercitam nosso código. Apesar de não serem a garantia de um software sem bugs, ter uma porcentagem alta de cobertura pode evitar muitas dores de cabeça no desenrolar do projeto.

No Android, para gerar esse tipo de relatório utilizamos o Jacoco (Java Code Coverage), uma das ferramentas mais utilizadas dentro do Java para esse propósito. No ambiente de desenvolvimento do Android, temos um fator dificultador que é o fato de possuirmos dois artefatos de teste diferentes, geralmente representados pelas pastas test (unitários) eandroidTest (instrumentados).

Primeiramente, vamos gerar o relatório de cobertura dos testes instrumentados do Espresso. Para este exemplo, teremos uma Activity bem simples:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView text;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(this);
        findViewById(R.id.hide).setOnClickListener(this);
        text = (TextView) findViewById(R.id.text);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.button) {
            text.setText("Hello World!");
        } else {
            v.setVisibility(View.GONE);
        }
    }
}

O layout dessa Activity é o seguinte:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Click Me!" />

    <Button
        android:id="@+id/hide"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Don't Click Me!" />

</LinearLayout>

Vamos então criar um teste no Espresso para garantir que o texto do TextView text seja modificado para Hello World! ao clicarmos no Button button:

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityTest {

    @Rule
    public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void shouldUpdateTextAfterButtonClick() {
        onView(withId(R.id.button)).perform(click());

        onView(withId(R.id.text)).check(matches(withText("Hello World!")));
    }
}

Após a execução desse teste, temos o relatório de execução:

Captura de tela de 2015-12-22 13-58-54

Porém, o relatório de cobertura ainda não é gerado. Para habilitarmos essa opção, precisamos adicionar uma propriedade para a nossa build variant de debug. Na DSL do plugin do Android, habilite a cobertura por meio da propriedade testCoverageEnabled:

android {
    ...
    buildTypes {
        debug {
            testCoverageEnabled true
        }
        ...
    }
}

Agora, basta executar a task createDebugCoverageReport para que os testes sejam executados e o relatório seja gerado.

Captura de tela de 2015-12-22 14-12-19

Perfeito! Já temos o relatório de cobertura do nosso projeto.

Vamos agora criar um teste usando o Robolectric para testar o else da lógica da nossa Activity. Porém, um aviso: particularmente, eu não recomendo testar a Activity e componentes do Android diretamente pelo Robolectric. Prefira testes que, de fato, testem unidades de código.

Um teste com Robolectric que testa o comportamento do botão hide, cuja visibilidade é alterada quando clicado, ficaria assim:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {

    @Test
    public void shouldHideButtonAfterClick() {
        MainActivity activity = Robolectric.setupActivity(MainActivity.class);

        Button button = (Button) activity.findViewById(R.id.hide);
        button.performClick();

        assertThat(button.getVisibility(), is(View.GONE));
    }
}

Por padrão, o plugin do Android só gera o relatório de cobertura dos testes instrumentados. Para que seja possível gerar a cobertura dos testes unitários, é necessário criar uma task de execução do relatório manualmente:

apply plugin: 'jacoco'

task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {

    reports {
        xml.enabled = true
        html.enabled = true
    }

    jacocoClasspath = configurations['androidJacocoAnt']

    def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
    def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
    def mainSrc = "${project.projectDir}/src/main/java"

    sourceDirectories = files([mainSrc])
    classDirectories = files([debugTree])
    executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")
}

Com a criação da task jacocoTestReport, temos agora a geração do relatório de cobertura também para os testes unitários.

Captura de tela de 2015-12-22 14-26-25

Porém, permanece o problema: como ter a cobertura unificada do resultado dos dois grupos de testes?

A instrumentação realizada pelo Jacoco produz arquivos de execução que contêm os dados necessários para a criação do relatório (HTML, XML, etc). O grande problema é que o Espresso gera o arquivo .ec, enquanto que a execução dos testes unitários gera o arquivo .exec, ou seja, temos formatos de arquivos diferentes!

E agora? Como converter de um formato para o outro? A resposta, obtida depois de muita pesquisa, é: simplesmente não é necessário converter!

Como não temos acesso à task que configura a execução dos testes com o Espresso, precisamos garantir que ela seja executada primeiro, para que, na execução do relatório dos testes unitários, o arquivo coverage.ec já esteja disponível.

Na task do relatório dos testes unitários, o que vamos fazer é adicionar o arquivo coverage.ec também como parâmetro na propriedade executionData:

apply plugin: 'jacoco'

task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {

    reports {
        xml.enabled = true
        html.enabled = true
    }

    jacocoClasspath = configurations['androidJacocoAnt']

    def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
    def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
    def mainSrc = "${project.projectDir}/src/main/java"

    sourceDirectories = files([mainSrc])
    classDirectories = files([debugTree])
    executionData = files(["${buildDir}/jacoco/testDebugUnitTest.exec",
                           "${buildDir}/outputs/code-coverage/connected/coverage.ec"
    ])
}

Por fim, ao executarmos a task completa, com os testes instrumentados pelo Espresso primeiro, teremos o relatório unificado de cobertura.

gradle clean createDebugCoverageReport jacocoTestReport

Captura de tela de 2015-12-22 14-39-29

E é isso! O projeto de exemplo pode ser encontrado neste repositório do Github! #KeepCoding #KeepTesting

Captura de tela de 2015-12-21 10-08-11

Integrando Ferramentas de Análise de Código no Android — Parte IV: Lint

Olá pessoal! Finalizando a nossa série sobre ferramentas de análise de código no Android, vamos conhecer o Lint! Introduzido versão 16 do ADT, o Lint é uma ferramenta de análise estática de código que verifica os códigos e recursos de um projeto Android em busca de bugs e possibilidades de otimização, buscando um código mais conciso, seguro, performático, levando em consideração também pontos como usabilidade, acessibilidade e internacionalização.

Como ele já faz parte da própria SDK do Android, muito provavelmente você já se deparou com avisos relacionados a strings hard-coded, ausência da propriedade contentDescription em um ImageView e coisas do tipo. Te convido a executar a task check neste momento em seu projeto para ver o que o Lint tem a dizer sobre ele :)

O Lint é composto de um conjunto bem grande de regras (que aumentam a cada atualização das ferramentas), divididas nas categorias Correctiness, Correctiness:Messages, Security, Performance, Usability:Typography, Usability:Icons, Usability, Accessibility, Internacionalization e Bi-directional Text. Caso queira listar todas as regras disponíveis na versão atual do SDK, basta executar em um terminal o comando:

lint --list

Cada uma das regras, dentro da configuração padrão, é aplicada de acordo com uma severidade pré-definida, que pode ser configurada. Dentro do Android Studio, é possível verificar quais regras estão atualmente ativas, dentro das configurações, no caminho Editor -> Inspections, na seção Android Lint.

Captura de tela de 2015-12-21 08-51-35

Para a configuração do Lint, o plugin do Android permite que ela seja feita através de uma DSL específica, através do nó lintConfig. Como exemplo, vamos pegar um caso bastante comum, que é a verificação InvalidPackage que gera erros no Lint quando utilizamos o OkHttp em nosso projeto. Esse erro indica que estamos utilizando pacotes da JDK que não estão disponíveis no Android — no caso do OkHttp, ele utiliza algumas classes no pacote java.nio quando disponíveis. A forma mais simples de realizar essa configuração é ignorar a task diretamente no nó lintConfig:

android {
    ...
    lintOptions {
        disable 'InvalidPackage'
    }
}

Para alguns cenários onde uma configuração mais refinada do Lint necessita ser feita — seja ignorando algumas regras, seja modificando a severidade de alguma regra — podemos criar um arquivo XML e adicioná-lo na DSL. Um exemplo de arquivo XML para ignorar a mesma InvalidPackage seria assim:

<?xml version="1.0" encoding="UTF-8"?>
<lint>
  <issue id="InvalidPackage" severity="ignore" />
</lint>

Em seguida, basta referenciar o arquivo na configuração do Lint:

android {
  lintConfig {
    lintConfig file("${project.rootDir}/config/lint.xml")
  }  
}

Todas as opções disponíveis para a configuração do Lint podem ser encontradas na documentação oficial, sempre atualizada em relação as últimas versões do plugin do Android.

Como, por padrão, a execução do Lint já é ligada a task check, não precisamos fazer nenhuma configuração específica na dependência de tasks. Caso haja algum problema, ou mesmo mensagens de atenção, um relatório HTML / XML é gerado com mais informações sobre a execução, bem como detalhes sobre as ocorrências encontradas no código.

Captura de tela de 2015-12-21 10-08-11

E é isso pessoal! Encerramos aqui a nossa série de posts sobre ferramentas de análise de código. Como última dica, recomendo uma espiada no projeto Android App Quality, que traz todas as ferramentas que vimos integradas, além de algumas configurações extras, como cobertura de código.

Até a próxima! #KeepCoding

Integrando Ferramentas de Análise de Código no Android — Parte III: PMD

Olá pessoal! Dando continuidade a nossa série de posts, hoje vamos conhecer o matador de bugs, o PMD.

Apesar de parecer uma sigla, de fato, nem os criadores sabem o que PMD significa. Atualmente em sua versão 5.4.1, ele possui regras que denunciam desde try/catches vazios até código copiado.

Como o Gradle possui nativamente um plugin do PMD, sua integração é tão simples quanto as outras ferramentas que já vimos. Primeiramente, vamos aplicar o plugin no nosso arquivo build.gradle:

apply plugin: 'pmd'

Da mesma forma que fizemos com as outras ferramentas, uma recomendação é adicionarmos a task que vamos criar à task check:

check.dependsOn 'checkstyle', 'findbugs', 'pmd'

O próximo passo é criar a nossa task. Ela é muito semelhante àquela que criamos para o FindBugs, com a diferença de que as propriedades que utilizamos para configurar as regras de validação se chamam ruleSetsruleSetFiles. Da mesma forma que o FindBugs, com o PMD também temos a possibilidade de gerar o relatório tanto em formato XML quanto HTML.

A task completa fica assim:

task pmd(type: Pmd) {
    ignoreFailures = false
    ruleSetFiles = files("${project.rootDir}/config/pmd-ruleset.xml")
    ruleSets = []

    source 'src'
    include '**/*.java'
    exclude '**/gen/**'

    reports {
        xml.enabled = false
        html.enabled = true
        html {
            destination "$project.buildDir/reports/findbugs/findbugs.html"
        }
    }
}

O arquivo de configuração pmd-ruleset.xml contém as regras que serão aplicadas ao processo de validação dos nossos arquivos. Um exemplo:

<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Android Application Rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd">

    <exclude-pattern>.*/R.java</exclude-pattern>
    <exclude-pattern>.*/gen/.*</exclude-pattern>

    <rule ref="rulesets/java/android.xml" />
</ruleset>

Todas as regras disponíveis para o PMD podem ser encontradas aqui. E como o próprio pessoal do PMD diz, nenhuma regra é escrita na pedra, é possível remover determinadas verificações de um ruleset. No contexto do Android, um caso onde isso é necessário é quando adicionamos a regra de Import Statements e estamos utilizando o Espresso, cujo design visa a fluência na leitura dos testes escritos. Esse design muitas vezes acaba gerando um grande número de imports estáticos, o que entra em conflito com a regra TooManyStaticImports do PMD. Caso haja a necessidade de remover alguma regra de um ruleset, podemos fazer isso no arquivo de configuração desta forma:

<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Android Application Rules" xmlns="http://pmd.sf.net/ruleset/1.0.0" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd">

    <exclude-pattern>.*/R.java</exclude-pattern>
    <exclude-pattern>.*/gen/.*</exclude-pattern>

    <rule ref="rulesets/java/android.xml" />
    <rule ref="rulesets/java/imports.xml">
        <exclude name="TooManyStaticImports" />
    </rule>
</ruleset>

Executando a task pmd em um terminal (ou utilizando o menu Gradle dentro do Android Studio), podemos ter uma análise de código baseada nas regras que configuramos.

Pegando como exemplo o ruleset android que adicionamos a nossa configuração, podemos simular o erro comum de utilizar a localização da memória externa hardcoded como /sdcard. Assim, temos como resultado o seguinte relatório:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ‘:app:pmd’.
> 1 PMD rule violations were found. See the report at: file:///Users/rafael/Project/quality-tools/app/build/reports/findbugs/findbugs.html

* Try:
Run with — stacktrace option to get the stack trace. Run with — info or — debug option to get more log output.

BUILD FAILED

O conteúdo do relatório é o seguinte:

1-aEobIoDE03qbUKvJPBnIOw

O link no relatório leva até a descrição detalhada do problema no próprio site do PMD.

E é isso, pessoal! Na próxima e última parte dessa série de posts, veremos como realizar um ajuste fino da execução do Lint para melhorar a qualidade do nosso aplicativo e evitar que problemas conhecidos e más práticas acabem afetando o desempenho em produção. Até lá!

Integrando Ferramentas de Análise de Código no Android — Parte II: FindBugs

Depois de um hiato um pouco maior que o esperado, estamos de volta com a nossa série sobre ferramentas de análise de código no Android. Conforme antecipado no post anterior, desta vez vamos falar sobre o FindBugs.

Atualmente em sua versão 3.0.1, o FindBugs é uma ferramenta de análise que se concentra em bugs comumente encontrados em códigos Java, além de buscar por potenciais problemas de segurança. A partir da análise, ele pode gerar relatórios em diversos formatos, inclusive HTML, que podem posteriormente ser publicados em um servidor de CI.

A integração da ferramenta ocorre de forma bastante semelhante ao CheckStyle, já que o Gradle também possui nativamente um plugin para o FindBugs.

O primeiro passo é aplicarmos o plugin ao nosso arquivo build.gradle:

apply plugin: 'findbugs'

Assim como fizemos com o CheckStyle, podemos adicionar a task do FindBugs a nossa task check.

check.dependsOn 'checkstyle', 'findbugs'

Feito isso, vamos então criar a nossa task. Como o FindBugs também verifica os arquivos .class, precisamos fazer com que essa task dependa de uma outra task de build. Neste exemplo, coloquei a task assembleDebug.

task findbugs(type: FindBugs, dependsOn: assembleDebug) {
    ignoreFailures = false
    effort = 'max'
    reportLevel = 'high'
    excludeFilter = new File("${project.rootDir}/config/findbugs-filter.xml")
    classes = files("${project.rootDir}/app/build/intermediates/classes")

    source 'src'
    include '**/*.java'
    exclude '**/gen/**'

    reports {
        xml.enabled = false
        html.enabled = true
        html {
            destination "$project.buildDir/reports/findbugs/findbugs.html"
        }
    }

    classpath = files()
}

No caso do FindBugs, primeiro configuramos para que o processo de build seja interrompido caso alguma ocorrência seja encontrada. Em seguida, configuramos os níveis de esforço e report, as classes que devem ser ignoradas (já veremos como é este arquivo) e o diretório onde estão as classes a serem analisadas. Configuramos o atributo source, adicionamos todos os arquivos java que forem encontrados e ignoramos quaisquer arquivos que estejam na pasta gen (o que geralmente não ocorre em projetos Gradle — essa propriedade faz mais sentido em builds Ant feitas pelo Eclipse).

O arquivo de filtro (findbugs-filter.xml) diz quais classes devem ser ignoradas no processo. Para este exemplo, utilizei um arquivo assim:

<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
    <Match>
        <Class name="~.*\.R\$.*" />
    </Match>
    <Match>
        <Class name="~.*\.Manifest\$.*" />
    </Match>
    <!-- Todas as classes de teste, exceto bugs específicos do JUnit -->
    <Match>
        <Class name="~.*\.*Test" />
        <Not>
            <Bug code="IJU" />
        </Not>
    </Match>
</FindBugsFilter>

Nesse arquivo, ignoramos a classe R, classes de Manifest e ignoramos bugs referentes ao JUnit nas classes de teste.

Por fim, configuramos a geração do relatório HTML e o classpath.

E pronto! Ao executarmos a task findbugs ou mesmo a task check (já que criamos esta dependência), teremos a execução da ferramenta sobre a nossa base de código. Mas, o que pode dar errado no código? Imagine o seguinte:

public class Client implements Parcelable {
  
  public static TABLE_NAME = "clients";
  
  ...
}

Tenho uma classe de modelo na qual, para organizar melhor meu projeto, coloquei um atributo estático com o nome da tabela do SQLite que armazena objetos desse tipo. Tudo certo, não?

findbugs-report

AHN??? Como assim?

Vamos ver o que o FindBugs tem a dizer sobre isso:

net.rafaeltoledo.sample.Client.TABLE_NAME isn’t final but should be

E ele ainda nos dá mais alguns detalhes sobre o motivo disso ser ruim:

MS_SHOULD_BE_FINAL: Field isn’t final but should be

This static field public but not final, and could be changed by malicious code or by accident from another package. The field could be made final to avoid this vulnerability.

Ahhh, sim! Me esqueci do modificador final, já que esse cara é uma constante! Ufa! 😀

O FindBugs possui uma lista vasta de verificações, e pode ser um grande aliado para fazer com que nosso código seja mais eficiente e mais seguro!

Então é isso, pessoal! No próximo post vamos conhecer o atirador de elite do nosso grupo, o PMD!

Até lá!

Integrando Ferramentas de Análise de Código no Android — Parte I: Checkstyle

Sempre que começamos a escrever código, por mais simples ou complexo que seja, sempre existe a preocupação com a qualidade. Quando trabalhamos em equipe, essa preocupação se torna ainda mais presente, pois são várias pessoas produzindo código, cada uma ao seu estilo.

Para melhorar um pouco esse cenário já conhecido, temos ferramentas que nos auxiliam a manter o código em um nível adequado de qualidade, em diversos aspectos. Nesta série que começamos hoje, vamos conhecer ferramentas que podem nos auxiliar a manter nosso código Android mais adequado.

Começando, vamos conhecer o Checkstyle. Conforme citado anteriormente, principalmente quando trabalhamos em um projeto com mais de uma pessoa, o código tende a adquirir traços da “personalidade” de cada um, seja através de indentação com tabs / espaços, posicionamento da chave, e até nomenclatura de classes e atributos. O Checkstyle tem por objetivo auxiliar a manter um padrão de código por todo o projeto, apontando trechos que estejam fugindo do padrão configurado.

O mais interessante, nesse caso, é que o Gradle, o nosso querido build system do Android, já possui o Checkstyle integrado ao projeto, necessitando apenas uma pequena configuração para que ele seja executado.

Bom, para começarmos, primeiramente precisamos aplicar o plugin doCheckstyle no nosso arquivo build.gradle.

apply plugin: 'checkstyle'

Uma prática que geralmente adoto nessa configuração, é adicionar o Checkstyle a task check do Gradle. Assim, além das demais verificações que já possuímos (como a execução do Lint), teremos também o Checkstylerodando.

check.dependsOn 'checkstyle'

Feito isso, vamos configurar a execução dessa task em nosso projeto.

task checkstyle(type: Checkstyle) {
    configFile file("$project.rootDir/config/checkstyle/checkstyle.xml")
    source 'src'
    include '**/*.java'
    classpath = files()
}

Nessa configuração, temos primeiramente a propriedade configFile que recebe o arquivo checkstyle.xml que é quem define o estilo do nosso projeto (já já veremos como esse arquivo é). Posteriormente, configuramos para que a execução seja feita sobre a nossa pasta source, incluindo todos os arquivos .java, e concluímos com a configuração do classpath, exigida pelo plugin. Vamos conhecer então o arquivo checkstyle.xml.

Esse arquivo é que conterá as regras a serem validadas pelo CheckStyle. Para esse primeiro exemplo, vamos adicionar apenas uma, que validará se não existem espaços entre os símbolos < e > na declaração de generics.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">

    <module name="TreeWalker">
        <module name="GenericWhitespace" />
    </module>

</module>

Existe uma infinidade de regras que podem ser adicionadas ao arquivo. Todas elas podem ser encontradas na documentação oficial, e podem ser aplicadas de acordo com o estilo a ser utilizado em seu projeto.

Para validar que a regra está funcionando, podemos criar uma classe simples, que viole a regra de estilo especificada:

package net.rafaeltoledo.codequality;

import android.support.v7.app.AppCompatActivity;
import java.util.List;

public class MyActivity extends AppCompatActivity {

    List< String > myList;
}

Executando a task checkstyle através do Gradle (pela linha de comando ou pelo menu Gradle, geralmente localizado na barra direita do Android Studio), teremos uma mensagem ao final da execução:

Executing external task ‘checkstyle’…
Parallel execution is an incubating feature.
[ant:checkstyle] [...]MyActivity.java:9:10: ‘<’ é seguido de espaço em branco. [ant:checkstyle] [...]MyActivity.java:9:17: ‘>’ é precedido por espaço em branco.

:app:checkstyle FAILED

FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ‘:app:checkstyle’.
> Checkstyle rule violations were found. See the report at: file:///[...]/app/build/reports/checkstyle/checkstyle.xml

* Try:
Run with — stacktrace option to get the stack trace. Run with — info or — debug option to get more log output.

BUILD FAILED

Total time: 1.803 secs

Além de reportar os problemas no console, ele ainda gera um relatório XML com todas as violações das regras!

Corrigindo o nosso código para:

List<String> myList;

Temos uma execução tranquila e sem erros :)

Executing external task ‘checkstyle’…
Parallel execution is an incubating feature.
:app:checkstyle

BUILD SUCCESSFUL

Total time: 2.622 secs

E é isso! No próximo post vamos conhecer a FindBugs, que pode nos salvar de bugs clássicos e problemas de segurança no nosso código 😀

Até lá!

Kotlin

Desenvolvendo para Android com Kotlin: Como Começar?

Olá pessoal!

Acredito que alguns de vocês já tenham ouvido falar no Kotlin. Pra quem não conhece, o Kotlin é uma linguagem que vem sendo desenvolvida pela Jetbrains (a empresa responsável pelo Android Studio, IntelliJ IDEA e uma porção de IDEs muito boas), focada na JVM, Android e web. A princípio, ela pode ser vista como um Java menos (muito) verboso. Por alguns, ela é chamada o Swift do Android! :)

Apesar de ainda estar em desenvolvimento, já é possível criar aplicativos completos utilizando essa linguagem, e é exatamente esse o propósito desse post: mostrar como configurar a sua IDE e criar o primeiro projeto usando Kotlin.

Bom, o primeiro passo é instalar os plugins do Kotlin no seu Android Studio ou IntelliJ. Para isso, abra as configurações da IDE e localize a opção Plugins. Ali, clique em Install JetBrains plugin… e localize os plugins Kotlin e Kotlin Extensions for Android.

Kotlin PluginsFeito isso, basta reiniciar a sua IDE para que a instalação esteja completa.

Com isso, já podemos criar o nosso projeto Android. A criação em nada difere de um projeto comum. Como ainda não temos um template, no início, temos que converter as classes manualmente. O que difere aqui são os arquivos de configuração do Gradle, que terão os plugins do Kotlin adicionados:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.1'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.12.1230'
        classpath 'org.jetbrains.kotlin:kotlin-android-extensions:0.12.1230'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

Aqui, adicionamos ao classpah os plugins do Kotlin (kotlin-gradle-plugin e kotlin-android-extensions – que não será utilizado ainda nesse primeiro tutorial).

No build.gradle do projeto, aplicamos o plugin e adicionamos a biblioteca padrão do kotlin:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.1'

    defaultConfig {
        applicationId 'net.rafaeltoledo.hellokotlin'
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName '1.0'
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'org.jetbrains.kotlin:kotlin-stdlib:0.12.1230'
}

Caso tenha escolhido para que o Android Studio gere a primeira Activity, você pode convertê-la para Kotlin indo até o menu Code -> Convert Java File to Kotlin File. Aquela sua Activity marota em Java vai virar algo mais ou menos assim:

package net.rafaeltoledo.hellokotlin.activity

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import net.rafaeltoledo.hellokotlin.R

public class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main)
    }
}

Agora é só executar e é só alegria. Até a versão 0.11, era necessária uma pasta de sources específica para o Kotlin, mas com a 0.12+, já não é necessário (perceba que os arquivos .kt ficam na pasta java de sources).

É isso pessoal! Aguardem que logo logo eu trago mais novidades!

Android-Security

Gerenciando suas Chaves no Android de forma Eficiente

Olá pessoal! Depois de um bom tempo sumido, estou de volta para falar de gerenciamento de chaves de assinatura no Android.

Beleza, você já desenvolveu seu aplicativo matador, que vai te gerar milhões em 2 dias, e agora vai gerar a sua build de release para publicar no Google Play. Nesse caso, precisamos criar a nossa assinatura de release no arquivo build.gradle do nosso projeto. Alguma coisa mais ou menos assim:

signingConfigs {
    release {
        storeFile file('/home/rafael/minha_key.jks')
        storePassword 'senha secreta'
        keyAlias 'app_android'
        keyPassword 'senha mais secreta ainda'
    }
}

Mas… tem alguma coisa errada aí. Principalmente se estamos utilizando algum controle de versão, como o Git ou SVN, não é uma boa prática enviarmos dados sensíveis para eles (como senhas, keystores e coisas do tipo). Durante algum tempo, a saída utilizada por mim foi adicionar esses dados ao arquivo gradle.properties na raiz do projeto, e não enviá-lo para o controle de versão.

STORE_FILE=/home/rafael/minha_key.jks
STORE_PASSWORD=senha secreta
KEY_ALIAS=app_android
KEY_PASSWORD=senha mais secreta ainda

Dessa forma, a nossa configuração de assinatura ficaria algo assim:

signingConfigs {
    release {
        storeFile file(STORE_FILE)
        storePassword STORE_PASSWORD
        keyAlias KEY_ALIAS
        keyPassword KEY_PASSWORD
    }
}

Funciona bem, não temos dados sensíveis no nosso controle de versão mas… temos basicamente dois problemas principais nessa abordagem:

  • No clone do projeto, você terá um erro de build. Ou seja, se entrou alguém na equipe e clonar o projeto, baterá com a cara na porta logo de cara. Isso já vai atrasar um pouco o join no projeto. Esse problema também se aplica quando o código está em um servidor de CI (Jenkins, Travis, CircleCI, etc.)
  • Tem o agravante humano, ou seja, sempre há o risco de, sem querer, você enviar o arquivo original por algum motivo. Obviamente que você pode adicionar este arquivo no .gitignore ou equivalente no SVN, mas nesse caso caímos no primeiro cenário problemático.

Então… qual a saída?

Depois de pensar um pouco, acabei encontrando, hoje, a melhor saída com o uso de variáveis de ambiente! Como assim?

Vamos lá: o primeiro passo é criarmos uma assinatura default para o projeto. No meu caso, eu simplesmente copio a debug.keystore (que fica dentro da pasta .android do  usuário) e coloco, por exemplo, dentro de um diretório chamado distribution na raiz do meu projeto. Essa chave vai garantir que a build não vai quebrar, por exemplo, quando alguém clonar o projeto ou em um servidor de CI, num primeiro momento.

Agora, na configuração da minha assinatura, podemos fazer algo do tipo:

signingConfigs {
    release {
        storeFile file(System.getenv('STORE_FILE') != null ? System.getenv('STORE_FILE') : '../distribution/debug.keystore')
        storePassword System.getenv('STORE_PASSWORD') != null ? System.getenv('STORE_PASSWORD') : 'android'
        keyAlias System.getenv('KEY_ALIAS') != null ? System.getenv('KEY_ALIAS') : 'androiddebug'
        keyPassword System.getenv('KEY_PASSWORD') != null ? System.getenv('KEY_PASSWORD') : 'android'
    }
}

Nota: caso seu servidor de CI esteja gerando builds de release, você pode setar as variáveis de ambiente nele também para gerar builds assinadas corretamente.

E pronto! Agora, para gerar a build corretamente, você pode exportar as variáveis de ambiente ou, como eu faço, colocar os dados da chave no comando apenas na hora de gerar a build de release:

$ STORE_FILE='/home/rafael/minha_key.jks' STORE_PASSWORD='senha secreta' KEY_ALIAS='app_android' KEY_PASSWORD='senha mais secreta ainda' ./gradlew clean assembleRelease

 E é isso! Espero que a dica seja útil para vocês e, caso tenham sugestões, podem postar aí nos comentários!

Até a próxima! 😀