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!