No mundo do desenvolvimento de softwares, sempre há situações em que acontecem coisas que nós não prevemos ou até mesmo que nunca pensaríamos que poderia acontecer, e por causa disso, sempre aparecem altos bugs, crashes e usuários reclamando de nossas aplicações.

Isso ocorre principalmente porque não testamos todas as possibilidades na hora de desenvolver, simplesmente porque estamos preocupados apenas com o “fluxo feliz” e nos esquecemos de verificar os infinitos erros que podem ocorrer (falta de conexão com internet, usuário fechar o aplicativo no meio de uma transação bancária, não contar que o usuário tenha limpado o cache do aplicativo, etc ).

Por causa disso, os testes foram evoluindo e hoje é possível testar todos os aspectos de um aplicativo, desde um simples teste unitário que verifica a lógica de forma isolada do resto; até os de tela que conferem o que acontece se o usuário clicar em um botão mil vezes seguidas e finalmente se o fluxo completo funciona: desde o login, comunicação com servidores, pagamento, até o fim da operação, certificando que o email foi enviado com a mensagem correta.

Nesse artigo vamos cobrir os Testes de Instrumentação e os Unitários no Android Studio. Ambos devem ficar em diretórios fonte separados, já que não vão ser incluidos no .apk final que vai ser publicado e nem as bibliotecas de teste devem estar nesse pacote.

Testes de Instrumentação

Estes rodam diretamente no dispositivo (ou emulador) e que testam  ao vivo como o aplicativo vai se comportar. O Android Studio o instala e só depois roda os testes. São úteis para testar as telas, botões e verificar o fluxo se por exemplo um botão de próxima fase em um jogo está efetivamente indo para a próxima fase e retornando o resultado esperado (salvou os pontos ganhos corretamente, etc).

Esses testes devem ser colocados em um pacote fonte com o nome “androidTest” para diferenciá-los dos testes unitários. Adicionalmente é necessário modificar a sua forma de execução escolhendo a variação de construção correta:

android-instrumentation-test
Escolha “Android Instrumentation Tests”

Porém antes de começar a desenvolver, é necessário dizer qual biblioteca de testes irá usar, neste exemplo utilizarei o padrão da própria plataforma, incluindo a dependência no build.graddle:

defaultConfig {
...
testApplicationId "com.myapp.test"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
...
}

O InstrumentationTestRunner roda diversos tipos de testes funcionais, portanto deve-se escolher qual o tipo de teste para determinado fim, por exemplo, se for testar uma Activity ou uma Application, deve-se utilizar o teste para esse fim específico. Mas também é possível testar diversos componentes distintos em uma classe de caso de testes ou realizá-lo de forma isolada, parecido com um teste unitário que será explicado depois. Abaixo listo os tipos de testes disponíveis nessa biblioteca:

Só que essa não é a única biblioteca de instrumentação disponível, no lugar do InstrumentationTestRunner podemos utilizar outras bibliotecas de acordo com a necessidade:

  • AndroidJUnitRunner:  Compatível com o JUnit 4, também suporta testes unitários.

  • Espresso: Muito recomendado para testes funcionais de telas.

  • UI Automator: Outro framework para testar a interface do usuário; muito utilizado para testes funcionais entre aplicativos instalados no dispositivo do usuário.

Uma confusão frequente que acontece é que testes de instrumentação e testes unitários tem configurações diferentes na hora de rodar, portanto, se por algum acaso você pegar um erro na execução como esse abaixo, é porque provavelmente está utilizando a configuração errada. Para conferir a configuração correta, simplesmente veja se está aparecendo “Android Tests” e não “Unit Tests” no menu de configuração de execução.

Exception in thread “main” java.lang.NoClassDefFoundError: junit/textui/ResultPrinter
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:190)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:122)
Caused by: java.lang.ClassNotFoundException: junit.textui.ResultPrinter

Erro ao executar um teste de instrumentação como unitário

menu-editar

Menu Editar Configurações de Execução

Abaixo segue uma classe de testes de Instrumentação (código fonte disponível no final do artigo) que testa o clique no botão e verifica se o resultado esperado está correto.

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {

   private MainActivity mainActivity;
   private Button button;

   public MainActivityTest() {
       super(MainActivity.class);
   }

   // Inicializa os componentes que serão testados
   @Override
   protected void setUp() throws Exception {
       super.setUp();
       mainActivity = getActivity();
       button = (Button) mainActivity.findViewById(R.id.button);
   }

   // Boa prática, verifica se os componentes foram incializados corretamente antes de continuar
   public void testPreconditions() {
       assertNotNull("mainActivity is null", mainActivity);
       assertNotNull("button is null",button);
   }

   // Testa o clique no botão, essa anotação é necessária porque o componente foi criado pela UIThread
   @UiThreadTest
   public void testButtonClick() {
       button.performClick();
       button.performClick();

       assertEquals("Clicado 2 vezes!",button.getText());
   }
}

Por último, já que o diretório fonte não será incluído na distribuição final do aplicativo, faz sentido também excluir as bibliotecas que são utilizadas apenas para executar os testes do pacote final. Para isso é possível marcar no build.graddle que as dependências serão utilizadas apenas na compilação dos testes de instrumentação, como no exemplo abaixo:

dependencies {
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}

Testes Unitários

Estes rodam de forma isolada, preferencialmente testando apenas uma parte pequena da lógica do código como um todo. Para isso é preciso desenvolver seu código pensando em como testá-lo e simular a execução de outros componentes mais complexos, os fazendo retornar valores fixos e falsos (Mock). A vantagem desse tipo de teste é que ele é mais rápido de executar que os de instrumentação, já que não é necessário a instalação do aplicativo no dispositivo e nem utilizar componentes reais.

Da mesma forma que os testes de instrumentação, os testes unitários devem ser colocados em um pacote fonte separado, só que desta vez, com o nome “test”, justamente para diferenciá-los. Adicionalmente é necessário modificar a sua forma de execução, escolhendo a variação de construção correta:

unity-tests

Escolha “Unit Tests”

Robolectric

Robolectric é uma biblioteca adequada para testes unitários em componentes de tela. De forma similar aos testes de instrumentação, que utilizaram uma biblioteca específica, os testes unitários também precisam de uma biblioteca para serem executados corretamente. Existem diversas bibliotecas além do Robolectric que utilizarei neste exemplo, as mais notáveis que valem a pena serem mencionadas são:

Framework Robotium UiAutomator Espresso Appium
Suporta testes em WebViews? SIM Limitado apenas à cliques NÃO SIM
Linguagem suportada Java Java Java Java e Ruby
Ferramenta de automação Testdroid Recorder Ui Automator Viewer Hierarchy Viewer Appium.app
Versões de api suportadas Todas >= 16 8, 10 e >=15 Todas
Comunidade Contribuições Google Google Ativa

A maior vantagem dos testes unitários em detrimento aos  testes de interface é o tempo de execução, já que os de instrumentação são muito demorados e por muitas vezes queremos testar algo bem específico que não necessita carregar todo o aplicativo antes. Além disso, percebam a simplicidade de como o mesmo teste que foi efetuado anteriormente possa ser executado pelo roboelectric:

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

   @Test
   public void testButtonClick() throws Exception {
       Activity activity = Robolectric.setupActivity(MainActivity.class);

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

       assertEquals("Clicado 2 vezes!",button.getText());
   }
}

Uma particularidade da divisão entre os tipos de testes é que os unitários utilizam outra nomenclatura para excluir as dependências do .apk na distribuição final, além disso, essa é a forma como se declara a dependência do robolectric no build.graddle:

dependencies {
    testCompile 'org.robolectric:robolectric:2.4'
    testCompile 'junit:junit:4.+'
}

Mas o que pode ser testado ?

Se estiver criando layouts de forma dinâmica, uma ideia é usar os testes unitários para testar se a lógica está correta e os componentes estão indo para o lugar certo sem precisar executar a aplicação inteira toda vez para conferi-los em tela, por exemplo, abaixo verificamos se o botão tem altura e largura correta:

ViewGroup.LayoutParams layoutParams =
            clickMeButton.getLayoutParams();
    assertNotNull(layoutParams);
    assertEquals(layoutParams.width, WindowManager.LayoutParams.MATCH_PARENT);
    assertEquals(layoutParams.height, WindowManager.LayoutParams.WRAP_CONTENT);

Adicionalmente, é possível verificar o comportamento do botão como nos exemplos anteriores, verificar a validação de um EditText caso o campo esteja esperando que o usuário insira um email por exemplo, etc.

Considerações Finais

Neste breve tutorial, apresentei a diferença entre testes de instrumentação que rodam no próprio dispositivo ou emulador e testes unitários que isolam a lógica específica simulando o comportamento de componentes complexos retornando valores fixos e falsos (Mock).

A importância de bons testes não deve ser subestimada, já que eles podem não só fazer o desenvolvedor realizar seu trabalho de forma mais rápida e precisa, mas também evitar muitos bugs que acabam passando batido por só pensarem no fluxo feliz.

Embora normalmente os testes de interface de usuário sejam feitos em sua grande maioria por ferramentas que criam todos os casos de testes automaticamente, acho válido os desenvolvedores aprenderem o básico para depois se aventurarem no automático. Claro que a automação de testes é um assunto para um outro artigo 😉

O Aplicativo com os testes está disponível no GitHub do AndroidProBlog.

Se você quiser aprender ainda mais sobre Desenvolvimento Android, dá uma olhada nesse Treinamento Completo, que foi preparado para cobrir absolutamente todos os elementos que você precisa saber para desenvolver aplicativos Android. Do básico ao avançado, e não precisa ser um gênio da programação para começar 😉

Leia também


Fillipe Cordeiro
Fillipe Cordeiro

Engenheiro da computação e desenvolvedor de software a quase 10 anos, com experiência em tecnologias como Java, Python e Android. Agora, quero te ajudar a mergulhar no universo do Desenvolvimento Android.