Android

22 nov, 2016

Testes no Android como Espresso – Parte 07

Publicidade

Dicas finais, TestButler e Robots Patterns

Ufa! Chegamos ao fim da nossa série sobre testes no Android com Espresso. Na última parte, aprendemos como criar custom matchers e como tratar run time permissions com UiAutomator. Agora vou focar mais em algumas dicas que acho importantes e também vou falar sobre TestButler e Robots Pattern. Para acompanhar, utilize o branch ‘part_7’ do projeto.

Animações de Loading x AlertDialogs

Cuidado com animações que mostram progresso (loadings) – especialmente se um dialog estiver por cima desta animação. Quando o dialog está por cima, a animação ainda está acontecendo na UiThread. Ou seja, o estado idle, que o Espresso fica esperando para poder seguir com o teste, nunca acontece. Alterei a LoginActivity para poder demonstrar isso. Repare como ficou o método de click do botão de login. Desconsidere o layout e a funcionalidade desta alteração, a ideia é apenas demonstrar o problema:

loginButton.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    <strong class="markup--strong markup--pre-strong">showLoading();</strong>
    if (validateFields())
      doLogin();
    else
      showErrorDialog();
  }
});

Com essa alteração, toda vez que o usuário clicar no botão de login, o loading é exibido na tela e, depois disso, fazemos o processo de login. O problema é que quando os campos estão vazios, o dialog de erro é exibido, e isso nos coloca exatamente na situação que mencionei anteriormente, um dialog em cima de um loading. Rode os testes da LoginActivity e repare que os testes que envolvem o dialog de erro quebram:

<strong class="markup--strong markup--pre-strong">AppNotIdleException</strong>: Looped for 3608 iterations over 60 SECONDS. The following Idle Conditions failed.

Portanto, sempre esconda os seus loadings da tela antes de exibir um dialog para o usuário, e sempre que um teste estiver falhando por conta de uma AppNotIdleException, verifique se não existe um loading “escondido” na sua tela.

Teste o comportamento

Foque em testar o comportamento do app. Não fique testando propriedades de layout. Se realmente precisar verificar as posições, use position assertions:

1-_yqxv5kcohzjayywvvzkxq
Position assertions from espresso-cheat-sheet

Execute seus testes em devices pequenos

Enquanto você roda seus testes em devices de tela grande, tudo ocorre muito bem, nada quebra. Mas não se esqueça que tem muita gente com dispositivos pequenos por aí, e que irão querer usar o seu app. Então, seja bonzinho! Lembre-se destas pessoas e execute seus testes em devices pequenos. Você vai perceber que muitos vão falhar, principalmente por conta de views que não estão na tela.

Em um dispositivo pequeno, o endereço do usuário não fica visível. O teste quebra
Em um dispositivo pequeno, o endereço do usuário não fica visível. O teste quebra

Você vai ter que usar bastante o método scrollTo().

Ação de toque único em devices/emuladores lentos (antigos)

No Google I/O deste ano, mais especificamente nesta palestra, apresentaram uma informação importantíssima, que é um problema de interpretação de toques únicos feitos pelo Espresso. Eles não entraram em detalhes, mas disseram que, em algumas vezes, o toque único é interpretado como um toque longo (long click) em dispositivos ou emuladores lentos. Isto acontece pois o toque em si é composto de duas ações: pressionar e soltar. Quando o dispositivo é muito antigo e lento, e tem alguma tarefa rodando em background, o tempo de resposta entre um evento e outro pode demorar, então este toque é interpretado como um long click. Para evitar que isto acabe quebrando seus testes, altere a seguinte configuração:

Settings -&gt; Accessibility -&gt; Touch and hold delay -&gt; LONG
Mude a propriedade destacada para LONG
Mude a propriedade destacada para LONG

O toast do Leak Canary

Leak Canary é uma lib para detectar leaks de memória no seu app. É uma ótima lib, mas de vez em quando lança automaticamente um toast na tela e, se isso acontecer durante a execução dos seus testes, eles vão quebrar, pois o toast vai bloquear a UI e o Espresso pode não encontrar a view com a qual ele está tentando interagir. Então, não instale o Leak Canary no device e/ou emulador que utilizará para fazer os testes.

TestButler, “descontaminando” o dispositivo para os testes

Enquanto eu escrevia esta série de artigos, o LinkedIn lançou uma lib chamada TestButler. Ela estabiliza o emulador em que os testes estão rodando, evitando que eles falhem por problemas nele. Por exemplo, você já deve ter se deparado com essa situação:

Isso não é legal =(
Isso não é legal =(

Isso com certeza quebrará seu teste. A ideia do TestButler é evitar que isso ocorra. Além disso, você também consegue alterar algumas configurações globais do emulador, como:

  • Habilitar/Desabilitar WiFi;
  • Mudar orientação do dispositivo;
  • – Definir o modo do location service (alta precisão ou economia de energia);
  • Definir o Locale (língua padrão) da aplicação, caso seu app seja destinado a mais de um país isso vai te ajudar bastante.

Enfim, vale a pena estudar e usar esta lib, com certeza vai ajudar bastante.

Robots Pattern

Semana passada assisti um tech talk na Concrete Solutions sobre Robots Pattern. Este padrão, apresentado por Jake Wharton, tem o objetivo de deixar os testes mais estáveis, legíveis e fáceis de dar manutenção. Por exemplo, um dos nossos testes da LoginActivity está desta forma:

@Test
public void whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  Intents.init();
 
  onView(withId(R.id.login_username)).perform(typeText("username"), closeSoftKeyboard());
  onView(withId(R.id.login_password)).perform(typeText("password"), closeSoftKeyboard());
  
  Matcher<Intent> matcher = hasComponent(MainActivity.class.getName());
  
  onView(withId(R.id.login_button)).perform(click());
  
  intended(matcher);
 
  Intents.release();
}

Aplicando Robots, o mesmo teste ficaria assim:

@Test
public void whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  LoginRobot login = new LoginRobot();
  ResultLoginRobot result = login
  		.username("defaultText")
  		.password("defaultText")
  		.login();
  result.isSuccess()
}

O código fica mais legível e você deixa na classe de testes somente os passos do teste em si. A maneira como estes passos serão implementados fica separado em outras classes:

class LoginRobot {
    LoginRobot username(String username) { 
    	onView(withId(R.id.login_username)).perform(typeText(username), closeSoftKeyboard());
    	return this;
    }
    
    LoginRobot password(String password) { 
	onView(withId(R.id.login_password)).perform(typeText(password), closeSoftKeyboard());
    	return this;
    }
    ResultLoginRobot login() {
    	Intents.init();
    	Matcher<Intent> matcher = hasComponent(MainActivity.class.getName()); 
    	onView(withId(R.id.login_button)).perform(scrollTo(), click());
    	return new ResultLoginRobot();
    }
}
 
class ResultLoginRobot { 
    ResultRobot isSuccess() {
    	onView(withId(R.id.login_button)).perform(click());
  	intended(matcher);
  	Intents.release(); 
    }
}

Na palestra, Jake Wharton implementa Robots com Kotlin. Se fizermos como ele, nosso teste ficaria bem mais limpo, por exemplo:

@Test
fun whenBothFieldsAreFilled_andClickOnLoginButton_shouldOpenMainActivity() {
  login {
  	username("defaultText")
  	password("defaultText")
  } doLogin {
  	isSuccess()
  }
}

Bem legal, né? E isso é só o começo! Dá para brincar bastante e melhorar a maneira como testamos nossos apps com este padrão. Assista a palestra toda, vale muito a pena.

Tenha paciência

Se é a primeira vez que você está dando a devida atenção aos testes, você vai achar que eles tomam muito tempo de desenvolvimento. Mas é o começo, e todo começo é difícil. Seus testes vão falhar aparentemente sem explicação nenhuma, vai descobrir que não é possível testar tudo somente com o Espresso (como foi o caso das RuntimePermissions), entre outros problemas que irão te fazer querer desistir. Mas não desista! Depois que você pega a prática, as coisas fluem melhor. Você passa a perceber algumas coisas, como:

  • Começa a desenvolver pensando em como você vai testar aquele código;
  • Se sente mais seguro ao fazer uma grande alteração no código, pois sabe que os seus testes estarão te cobrindo, caso você quebre algo;
  • Passa a estimar suas tarefas já pensando no esforço dos testes.

É muito importante ter uma cultura de teste, seja com Espresso ou não, seja um app mobile ou não. Fiz esta série pensando em incentivar os desenvolvedores a criar este hábito, tratar os testes como aliados e não como inimigos. Espero que tenha gostado do conteúdo que compartilhei ao longo destes posts. Se tiver alguma dúvida, sugestão ou crítica, fique à vontade para me procurar ou comentar abaixo. Obrigado!