Como fazer testes de aceitação no Android?

Nos cursos de Android da Caelum é bastante comum a seguinte pergunta: Como fazer testes de aceitação para uma aplicação Android no Eclipse?

É importante perceber que, como esse tipo de teste deverá utilizar as classes do Android, precisaremos chamar classes que estão definidas no android.jar. No entanto, as classes que estão definidas no android.jar não possuem implementação real, são apenas stubs para “enganar” o compilador, pois o código real do Android está nos aparelhos e emuladores.

Dessa forma, se quisermos rodar algum teste fora do ambiente do Android, ao chamar na mão algum método do android.jar receberemos uma RuntimeException("Stub!") para nos lembrar de que esses métodos não possuem implementação.

Então, como fazer um teste que utilize as classes do Android?

Primeiramente, vamos criar um novo Android Application Project contendo uma Activity com um menu:

public class PrimeiraActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstance) {
		super.onCreate(savedInstance);
		setContentView(R.layout.activity_primeira);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.menu, menu);
		
		return super.onCreateOptionsMenu(menu);
	}
}

O nosso menu.xml possuirá apenas um item chamado menu_segunda_activity:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

	<item
		android:id="@+id/menu_segunda_activity"
		android:showAsAction="always"
		android:title="Segunda Activity"/>

</menu>

Ainda na PrimeiraActivity, vamos dizer que ao clicar no item menu_segunda_activity, vamos abrir a SegundaActivity:

public class PrimeiraActivity extends Activity {

	//código digitado anteriormente...

	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		
		if(item.getItemId() == R.id.menu_segunda_activity) {
			Intent segunda = new Intent(this, SegundaActivity.class);
			startActivity(segunda);
		}
		
		return super.onMenuItemSelected(featureId, item);
	}
}

Vamos, agora, criar a SegundaActivity com o seguinte código:

public class SegundaActivity extends Activity{

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

Agora que temos nosso projeto principal pronto, podemos criar um teste para verificar se a SegundaActivity é realmente chamada no clique do options menu. Para isso, vamos criar um projeto novo do tipo Android Test Project: um projeto separado do projeto principal, que será o responsável pelos testes!

Criar esse projeto é tão simples quanto criar um Android Application Project: basta fazer

Ctrl+N -> Android -> Android Test Project

Na próxima tela, basta dar um nome a esse projeto e, ao clicar em Next, selecionar o projeto que será testado.

Com isso, já podemos criar nosso teste para verificar se a SegundaActivity realmente será chamada ao clicar no item do menu!

No nosso Android Test Project, vamos criar uma classe chamada PrimeiraActivityAcceptanceTest e torná-la filha de ActivityInstrumentationTestCase2. Além disso, temos que dizer no tipo genérico qual classe será testada (no caso, PrimeiraActivity):

public class PrimeiraActivityAcceptanceTest extends 
            ActivityInstrumentationTestCase2<PrimeiraActivity> {

	public PrimeiraActivityAcceptanceTest(){
		super(PrimeiraActivity.class);
	}
}

Como nosso teste deve simular um toque na tela para chamar o menu, vamos habilitar esse teste para usar a tela e recuperar uma instância da PrimeiraActivity para usar no teste. Além disso, como precisamos utilizar a infraestrutura do Android para processar esses eventos, precisamos de um elemento chamado Instrumentation, que justamente simula essa estrutura do Android para que possamos simular uma chamada a um item do menu.

Tudo isso pode ser feito no método setUp(), que é chamado antes dos testes:

public class PrimeiraActivityAcceptanceTest extends 
            ActivityInstrumentationTestCase2<PrimeiraActivity> {

	private PrimeiraActivity primeiraActivity;
	private Instrumentation instrumentation;

	public PrimeiraActivityAcceptanceTest(){
		super(PrimeiraActivity.class);
	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		setActivityInitialTouchMode(true);
		primeiraActivity = getActivity();
		instrumentation = getInstrumentation();
	}
}

Agora podemos fazer nosso teste!

Como o Android Test Project utiliza um runner criado sobre o JUnit 3 para rodar os testes, todos os nossos métodos de teste devem seguir as convenções do JUnit 3, como por exemplo, começar com “test”.

public class PrimeiraActivityAcceptanceTest extends 
            ActivityInstrumentationTestCase2<PrimeiraActivity> {

	private PrimeiraActivity primeiraActivity;
	private Instrumentation instrumentation;

	public PrimeiraActivityAcceptanceTest(){
		super(PrimeiraActivity.class);
	}

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		setActivityInitialTouchMode(true);
		primeiraActivity = getActivity();
		instrumentation = getInstrumentation();
	}

	public void testAoClicarNoMenuDeveChamarSegundaActivity() throws Exception {

	}
}

Nesse teste, vamos simular a chamada ao options menu

public void testAoClicarNoMenuDeveChamarSegundaActivity() throws Exception {
    
	sendKeys(KeyEvent.KEYCODE_MENU);
}

… clicar num item específico desse menu utilizando o instrumentation…

public void testAoClicarNoMenuDeveChamarSegundaActivity() throws Exception {
    
	sendKeys(KeyEvent.KEYCODE_MENU);

	instrumentation.invokeMenuActionSync(
				primeiraActivity, R.id.menu_activity_proxima, 0);
}

… e verificar se a Activity chamada é realmente a SegundaActivity. Mas como verificar que a SegundaActivity foi realmente chamada? O próprio instrumentation fornece uma forma de fazer isso: por meio de um ActivityMonitor.

A ideia é monitorar o teste e ver se a SegundaActivity aparece na tela até 1000ms depois
do clique no menu:

public void testAoClicarNoMenuDeveChamarSegundaActivity() throws Exception {

	ActivityMonitor monitor = instrumentation.addMonitor(
			SegundaActivity.class.getName(), null, false);
    
	sendKeys(KeyEvent.KEYCODE_MENU);

	instrumentation.invokeMenuActionSync(
				primeiraActivity, R.id.menu_segunda_activity, 0);

	SegundaActivity segundaActivity = (SegundaActivity) monitor
				.waitForActivityWithTimeout(1000);
}

Caso a SegundaActivity apareça, teremos sua referência na variável segundaActivity; do contrário, será null. Então, esse teste só deve passar se segundaActivity não for null:

public void testAoClicarNoMenuDeveChamarSegundaActivity() throws Exception {

	ActivityMonitor monitor = instrumentation.addMonitor(
			SegundaActivity.class.getName(), null, false);
    
	sendKeys(KeyEvent.KEYCODE_MENU);

	instrumentation.invokeMenuActionSync(
				primeiraActivity, R.id.menu_activity_segunda, 0);

	SegundaActivity segundaActivity = (SegundaActivity) monitor
				.waitForActivityWithTimeout(1000);

	assertNotNull(segundaActivity);
}

Nosso teste está pronto! Para rodar os testes, basta clicar com o botão direito no projeto e

Run As... -> Android JUnit Test

Esse projeto será instalado como um Android Application Project! Além disso, por ser um teste de aceitação, quando o Android executar o teste é possível ver a execução de cada passo na tela do aparelho ou emulador!

Tags: ,

3 Comentários

  1. Leonardo Santos 30/05/2014 at 09:40 #

    Era o que eu precisava pra mostrar pro pessoal!

  2. James Mendes Martins 02/06/2014 at 11:33 #

    Muito bom!!! Valeu.

  3. Raphael Lacerda 18/01/2015 at 13:20 #

    Então.. acho que rolaria um PageObjects bacana demais nesse caso então

Deixe uma resposta