Configurando o Ambiente de Desenvolvimento – Tutorial Android 1

Padrão

Desde o último post que explicava a configuração do ambiente de desenvolvimento para Android, tivemos pelo menos uma mudança bem drástica. Se antes utilizávamos o Eclipse, hoje utilizamos o Android Studio, uma versão da poderosa IDE IntelliJ IDEA, desenvolvida pela JetBrains.

Configuração no Windows

O primeiro passo para a instalação do ambiente no Windows, é o download da JDK (Java Development Kit). Ela nos fornecerá a base de ferramentas para o desenvolvimento Java, que será utilizada também para o desenvolvimento dos apps Android também. Você pode baixar a JDK no site da Oracle. A instalação deve ocorrer sem problemas.

O próximo passo consiste em baixarmos o Android Studio. Após baixar o .exe bundle (que já contém os itens básicos de SDK e emulador), basta seguir o procedimento de instalação normalmente como qualquer outro aplicativo.

O último passo é exportarmos as variáveis de ambiente JAVA_HOME ANDROID_HOME, para que possamos, quando necessário, também executar builds e comandos a partir do console. Primeiramente vamos exportar a ANDROID_HOME. Caso você não tenha alterado o local durante a instalação do Android Studio, o valor deve ser:

%LOCALAPPDATA%\Android\sdk

Em seguida, vamos exportar a JAVA_HOME. Novamente, se você não alterou o local da instalação do Java, o valor deve ser:

%ProgramFiles%\Java\jdk1.8.0_111

No momento que escrevo este post, a última versão do Java é a 8, revisão 111. Caso a versão que você instale seja outra, ajuste o caminho de acordo.

Por fim, vamos adicionar os binários ao Path do sistema, para que sejam acessíveis através do console. Adicione ao Path as entradas %ANDROID_HOME%\tools%ANDROID_HOME%\platform-tools%JAVA_HOME%\bin.

Para testar, valide que os comandos adbjavac são acessíveis no console.

Durante a instalação do Android Studio, também deve ter sido instalado o HAXM (caso o seu processador seja compatível). O HAXM permitirá que vocẽ execute o emulador do Android com excelente desempenho. Outra boa opção de emulador é o Genymotion, porém para usos gerais, o emulador que acompanha a SDK do Android é suficiente.

Por fim, opcional mas recomendado, instale o Git, que é hoje a ferramenta de versão mais utilizada.

Configuração no Linux

No Linux, por possuirmos um gerenciador de pacotes, a coisa fica um pouco mais fácil. Caso você esteja utilizando o Ubuntu / Linux Mint, para preparar nosso ambiente, basta instalar os seguintes pacotes:

sudo apt-get install lib32z1 lib32ncurses5 lib32stdc++6 openjdk-8-jdk qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils

Caso esteja utilizando o Fedora, os pacotes são:

sudo dnf install glibc.i686 glibc-devel.i686 libstdc++.i686 zlib-devel.i686 ncurses-devel.i686 libX11-devel.i686 libXrender.i686 libXrandr.i686 java-1.8.0-openjdk-devel qemu-kvm

Outras distribuições podem ter estes pacotes com outros nomes, mas uma rápida busca pode trazer os correspondentes. O Linux não utiliza o HAXM para aceleração dos emuladores x86 – para isso, temos o KVM, um mecanismo de virtualização ligado ao kernel (caso o seu processador suporte).

Em seguida, vamos baixar o Android Studio. Para Linux, o arquivo tem extensão .tar.gz e deve ser extraído para alguma pasta (como por exemplo, ~/Android). Feito isso, basta executar o arquivo studio.sh que encontra-se dentro da pasta bin. Na primeira execução, serão baixados os pacotes necessários para a SDK. Para encerrar, selecione no menu Configure a opção Create Desktop Entry para facilitar a execução do Android Studio a partir do seu menu de aplicativos (seja UnityGnomeKDE, etc.)

O último passo é exportarmos a variável ANDROID_HOME e colocar os binários no Path. Para isso abra o arquivo ~/.bashrc ou, caso esteja utilizando o zshell, ~/.zshrc, e adicione as seguintes linhas. Caso tenha modificado o local da instalação da SDK do Android, ajuste o caminho:

export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools"

Por fim, é opcional porém recomendada, a instalação do Git para controle de versão dos nossos projetos.

Configuração no OSX

No OSX (Mac), a instalação é semelhante a que fizemos para o Windows. Primeiramente, baixe a JDK do site da Oracle. A instalação deve ocorrer sem maiores problemas, bastando seguir o wizard com o passo a passo da instalação.

Em seguida, baixe o Android Studio. A partir do arquivo .dmg, a instalação é tranquila, seguindo o padrão para outros aplicativos do Mac. Ao executar pela primeira vez, as ferramentas serão baixadas e o HAXM será instalado.

O último passo é a configuração das variáveis de ambiente. Contando que os caminhos padrão tanto do Java quanto da SDK do Android não foram alterados durante a instalação, adicione o seguinte conteúdo ao arquivo ~/.bash_profile (caso o arquivo não exista, crie-o).

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

Por último, opcionalmente mas fortemente recomendado, instale o Git (opte por utilizar o Homebrew, para instalar a versão mais recente).


E é isso! Aguardem os próximos posts, pois começaremos a colocar a mão na massa 🙂

Nova Série de Tutoriais Android

Padrão

Olá pessoal! Quanto tempo, hein?

Nem parece, mas o tempo passa, e como passa! Os tutoriais de Android, que além de ajudar muita gente nesses últimos anos, também transformaram minha carreira e minha vida, já estão completando incríveis 5 anos! E como todos sabem, em se tratando de tecnologia, 5 anos é uma eternidade.

Pensando nisso, nesse ano de 2017 vou publicar a série novamente, revisitando, ampliando, e atualizando todo o seu conteúdo. Afinal, de 2012 pra cá muita coisa mudou, e o Android evoluiu como ninguém podia imaginar, não é verdade?

Então aguardem, pois em breve teremos muitas novidades aqui no blog!

MVA  –  Quando Menos é Mais

Padrão

*Este post foi originalmente publicado no Medium. Confira aqui.

Desde que comecei a programar profissionalmente, sete anos atrás, arquitetura sempre foi um dos assuntos que mais me chamaram a atenção. Até hoje me instiga a ler, pesquisar, experimentar e aprender muito com tudo isso.

Quando fiz a migração definitiva do Java backend para o Android, uma das coisas que mais me chamou a atenção é que tudo estava ainda sendo desenvolvido (desde as ferramentas e IDEs até as próprias ideias sobre organização do código em si). A arquitetura do próprio Android não chega a ser um primor, e muita gente vem trabalhando em melhores formas de se organizar um app sem tornar a tarefa da manutenção um trabalho árduo e desgastante.

Nessa vertente, duas abordagens surgiram há algum tempo e ganharam força nos últimos tempos: a MVP e a Clean Architecture. Não vou abordar conceitualmente as duas arquiteturas aqui, pois são assuntos que valem (mais de) um post inteiro cada. Inclusive, falei sobre várias abordagens e opiniões no Android Dev Conference 2015, e o Medium do Android Dev BR já teve posts sobre elas também (MVC/MVP e Clean Architecture).

Em ambas as abordagens criam-se camadas para aumentar a abstração e facilitar o isolamento de cada uma das partes do sistema, de acordo com suas responsabilidades. A separação em si melhora a capacidade de testar as partes de forma isolada, pois o uso de interfaces é bastante comum, o que facilita o mock para os testes e permite testar fluxos inteiros em alguns casos utilizando apenas testes unitários.

É aí que o negócio começou a me incomodar. Principalmente na abordagem da Clean Architecture, mesmo uma funcionalidade simples exige a criação de várias camadas e classes, adicionando uma camada de burocratização na aplicação. Não estou dizendo que ela não traga nenhum benefício, mas esse problema me incomoda e muito nos projetos que vejo utilizando esta arquitetura.

O MVP sofre menos com esse problema, mas ainda exige a criação de uma série de interfaces com as abstrações de cada uma das camadas, com a justificativa de melhorar a testabilidade do sistema. O meu problema principal neste caso é a completa ausência de testes (e testes relevantes) nas mais diversas implementações do padrão que já encontrei. Se as abstrações são a troca a ser realizada para que se tenha uma aplicação testável, por que o objetivo não está aparecendo no final?

Ainda relacionado ao MVP, um paradigma criado pensando-se no ambiente da Web que surgiu com força por volta de 2007, alguns dos benefícios não se aplicam tanto ao Android, como a possibilidade de troca transparente da implementação de View (trocar uma implementação de Activity por Fragment não chega a ser um problema em qualquer que seja a arquitetura implementada), ou mesmo a troca dos modelos por mocks para propósitos de demonstração ou testes — é possível implementar cenários como o “development drawer” de forma agnóstica.

Conversando com algumas pessoas e vendo alguns projetos sendo desenvolvidos, vi que muitas vezes as arquiteturas são implementadas sem entender os conceitos, simplesmente reproduzindo o que foi visto em algum outro projeto. Muitas vezes faltam conceitos, mas principalmente um pouco de questionamento: preciso de tudo isso?

No meio de tantos Ms e Vs, acabei me deparando com o MVA — o Minimum Viable Architecture. Uma das frases que definem o MVA diz que:

O melhor código que você pode escrever agora é o código que você irá jogar fora em alguns anos.

Apesar de ser um conceito voltado muito mais para o desenvolvimento de produtos do que programação em si, ele serve para questionarmos um pouco a forma como desenvolvemos os nossos apps hoje.

  • A arquitetura não é construída pensando nos piores cenários. Em vez disso, o produto é construído pensando nos cenários mais comuns.
  • Escreva código em pequenos incrementos através do tempo. Para que isso aconteça, as definições principais de requisitos e tecnologias já devem ter sido definidas.
  • Tudo que é desenvolvido deve ser baseado em requisitos concretos ou pelo menos razoáveis, não sobre achismos ou deduções.

Entendam estes conceitos como uma forma de implementações mais objetivas. Isso não quer dizer que a qualidade e organização deve ser deixada de lado. Nem que os testes devem ser ignorados nem que a aplicação não deve ter uma arquitetura e padrões definidos. Mas pensem e questionem sobre o que está sendo feito. A troca está valendo a pena? Quais objetivos estão sendo buscados com isso?

Enfim, essa é uma grande e excelente conversa, que só estou começando aqui. O Android está evoluindo e a nossa forma de pensar a respeito das nossas aplicações também deve evoluir. Não sou contra os vários MVs, mas o questionamento faz-se necessário.

E você, o que acha? Vamos conversar sobre arquitetura? 🙂

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

Padrão

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

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

Padrão

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

Padrão

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á!