Conceitos, práticas e motivações
Testes no Android
Eu
Chapter Lead de Android
www.rafaeltoledo.net
twitter.com/_rafaeltoledo
github.com/rafaeltoledo
blog.concretesolutions.com.br
Por que escrever Testes?
o dilema do programador mobile
Garantir que algo funciona da forma como deveria
Documentação de comportamento de um sistema
Garantir que uma mudança não quebra outras partes do app
A pirâmide refere-se ao desenvolvimento backend
Front-end em si é interface (GUI)
Ao contrário dos apps 100% offline, se muitas regras de negócio concentram-se no front-end, é sinal que sua arquitetura está errada
Por que tenho 2 pastas de Testes?
o que são as pastas test e androidTest no meu projeto?
Testes unitários / funcionais instrumentados, que necessitam das classes do Android para a execução.
São executados em emuladores ou devices reais
androidTest
Testes unitários executados na JVM (máquina local)
Componentes externos geralmente são mockados (como as classes do Android)*
test
Testes unitários executados na JVM (máquina local)
Componentes externos geralmente são mockados (como as classes do Android)*
Robolectric
escopos de dependências
// somente testtestCompile 'junit:junit:4.12'testCompile 'org.robolectric:robolectric:3.1.2'
// somente androidTestandroidTestCompile 'com.android.support.test:runner:0.5'androidTestCompile 'com.android.support.test:rules:0.5'
Bibliotecas e Frameworks
quem poderá nos ajudar?
Framework para criação de testes ‘repetíveis’
Estrutura da execução dos testes
Biblioteca de asserções
public class MeuTeste {
@Test public void stuffTest() { Assert.assertEquals(2, 1 + 1); }}
Biblioteca para a criação de asserções mais intuitivas e legíveis
Se tornou parte do JUnit
assertThat(1 + 1, is(2));
assertThat(lista, contains(2, 3, 4, 8);
String texto = "Android no TDC"assertThat(texto, containsString("Android");assertThat(texto, not(containsString("iOS");
Hamcrest
Biblioteca para a criação de asserções mais intuitivas, legíveis e fluentes
Possui uma extensão chamada AssertJ Android feita pela Square
assertThat(sociedadeDoAnel) .hasSize(9) .contains(frodo, sam) .doesNotContain(sauron);
// AssertJ AndroidassertThat(view).isGone();
AssertJ
Biblioteca para a criação de mocks
List mockedList = mock(List.class);
mockedList.add("one");mockedList.clear();
verify(mockedList).add("one");verify(mockedList).clear();
LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(0)).thenReturn("first");
// Vai mostrar "first"System.out.println(mockedList.get(0));
// Vai mostrar null, já que não mockamos o comportamentoSystem.out.println(mockedList.get(999));
Framework para a criação de testes Instrumentados no Android
Espresso
AndroidJUnitRunner
JUnit4 Rules
UI Automator
Android TestingSupport Library
Espresso
Biblioteca para a escrita de testes unitários de UI para o Android
onView(withId(R.id.name_field)).perform(typeText("TDC"));
onView(withId(R.id.greet_button)).perform(click());
onView(withText("Olá, TDC!")).check(matches(isDisplayed());
Android TestingSupport Library
AndroidJUnitRunner
Suporte ao JUnit 4, acesso a informações da instrumentação (contexto, execução, etc.), filtro de testes e distribuição
Rules
Possibilita testar Activity, Intent e Service
UiAutomator
Testes de UI no Android de forma “livre” Android TestingSupport Library
“Robolectric é um framework de testes unitários que desacopla a dependência do jar do Android, de forma que você possa fazer o desenvolvimento do seu aplicativo guiado por testes. Execute seus testes na JVM em segundos!”
É um simulador do ambiente de execução do Android
Testes são “instrumentados” na própria JVM
Robolectric
@Testpublic void clickingButton_shouldChangeResultsViewText() {
MyActivity activity = Robolectric.setupActivity(MyActivity.class);
Button button = (Button) activity.findViewById(R.id.button); TextView results = (TextView) activity.findViewById(R.id.results);
button.performClick(); assertThat(results.getText().toString()).isEqualTo("Hello!");}
Robolectric
Request Matcher
Biblioteca open-source para a criação de asserções das requests do app, utilizando em conjunto o Mock Web Server da Square
serverRule.enqueue(200, "body.json") .assertPathIs("/somepath") .assertNoBody() .assertMethodIs(RequestMatcher.GET);
github.com/concretesolutions/requestmatcher
Organização dos testes
porque teste também é código
Organização AAA
Arrange (Organizar): set-up dos testes, preparação dos objetos, etc.
Act (Agir): a execução, ou o exercício do comportamento propriamente dito
Assert (Confirmação): a verificação se o resultado da execução foi o esperado
Organização OCA
Organizar: set-up dos testes, preparação dos objetos, etc.
Agir: a execução, ou o exercício do comportamento propriamente dito
Confirmação: a verificação se o resultado da execução foi o esperado
Organização OCA
// OCalculator c = new Calculator();c.setFirstNumber(1);c.setSecondNumber(2);c.setOperation(Calculador.SUM);
// Cc.performOperation();
// AassertThat(c.getResult(), is(3));
Código difícil de testartestes podem denunciar problemas no design de classes
Design de classes
Calculator c = new Calculator();c.setFirstNumber(1);c.setSecondNumber(2);c.setOperation(Calculador.SUM);
c.performOperation();
assertThat(c.getResult(), is(3));
Design de classes
Calculator c = new Calculator.Builder() .firstNumber(1) .secondNumber(2) .operation(Calculador.SUM) .build();
c.performOperation();
assertThat(c.getResult(), is(3));
Por onde começar?
ok, conheço as ferramentas... mas o que eu testo?
Nossa cobaia será um app que consome a API do StackOverflow e lista os usuários com a maior reputação no site
Consumo de API com Retrofit
RecyclerView com endless scroll
Salvando dados na mudança de orientação!
app/build.gradle
defaultConfig { applicationId 'net.rafaeltoledo.tests' minSdkVersion 16 targetSdkVersion 23 versionCode 1 versionName '0.0.1'
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'}
app/build.gradle
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'androidTestCompile 'com.android.support.test:runner:0.5'androidTestCompile 'com.android.support.test:rules:0.5'
app/build.gradle
@RunWith(AndroidJUnit4.class)public class HomeActivityTest {
@Rule public ActivityTestRule<HomeActivity> activityRule = new ActivityTestRule<>(HomeActivity.class);
...}
androidTest/. . . /HomeActivityTest.java
public class HomeActivityTest {
@Test public void checkIfRecyclerViewIsLoading() { // ... }}
androidTest/. . . /HomeActivityTest.java
public class HomeActivityTest {
@Test public void checkIfRecyclerViewIsLoading() { // êpa! Calma aí... }}
Testes e Dependências Externascomo fazemos com a API? como fazemos com hardware? Como fazemos pra testar?
The FIRST Things for Unit Tests
Fast! (Rápidos): tem que ser executados em alguns milissegundos ou segundos (Android)
Isolated (Isolados): devem focar em uma porção pequena do código, alinhados com a definição de unitário
Repeatable (Repetíveis): produzem os mesmos resultados todas as vezes que você o executa
The FIRST Things for Unit Tests
Self-Validating (Auto-Validados): um teste só é um teste se ele se certifica de que as coisas estão certas. Testes não devem ter interação – devem poupar e não gastar seu tempo
Timely (Oportuno): testes, se não se tornarem um hábito, podem facilmente ser “esquecidos”. E, no futuro, dificilmente esse débito venha a ser solucionado.
Mock
Abordagens de Mock
Mock Objects: podemos programar o comportamento dos objetos para que respondam como desejamos (Mockito)
Mock Requests: deixamos que os objetos se comportem normalmente e somente apontamos para uma outra API (MockWebServer)
Mock objects
// Mockando a API com o mockitoStackApi api = mock(StackApi.class);when(api.getUsers(anyInt()).thenReturn(createMockedResponse());
// Mockando callbacksArgumentCaptor<Callback<ApiCollection<User>>> captor = forClass(Callback.class);verify(api.getUsersAsync(anyInt(), captor);captor.getValue().onSuccess(createMockedCall(), createMockedResponse());
Mock objects
@RunWith(MockitoTestRunner.class)
// Mockando a API com o mockito@MockStackApi api;
when(api.getUsers(anyInt()).thenReturn(createMockedResponse);
// Mockando callbacks@CaptorCallback<ApiCollection<User>>> captor;
verify(api.getUsersAsync(anyInt(), captor);captor.getValue().onSuccess(createMockedCall(), createMockedResponse());
Mock Server
// Mockando a API com o mockitoMockWebServer server = new MockWebServer();
server.enqueue(new MockResponse() .setBody(json) // string! .addHeader("Header", "value") .setResponseCode(200));
Mas...
Como fazer meu app utilizar esses objetos?
Mas...
Como fazer meu app utilizar esses objetos?
1. DI / Setter
Mas...
Como fazer meu app utilizar esses objetos?
1. DI / Setter
2. Reflection
DI / Setter
// API Mockada que criamos :)apiSingleton.setApi(mockApiObject);
DI / Setter
// API Mockada que criamos :)apiSingleton.setApi(mockApiObject);
Porém, não é bom quando modificamos o nosso código de produção por causa do teste.
Isso pode vir a gerar problemas na arquitetura ou brechas de segurança
DI / Setter
public class ApiSingleton {
// ...
@VisibleForTesting public void setApi(MyApiInterface api) { this.api = api; }}
DI / Setter
public class ApiSingleton {
// ...
@VisibleForTesting public void setApi(MyApiInterface api) { this.api = api; }}
PS: Dagger é uma boa saída pra fazer essa troca
Reflection
// Mudamos o valor do SingletonApiModule module = ApiModule.getInstance();
Field field = module.getClass().getDeclaredField("api");field.setAccessible(true);field.set(module, mockApi);
Reflection
// Mudamos o valor do SingletonApiModule module = ApiModule.getInstance();
Field field = module.getClass().getDeclaredField("api");field.setAccessible(true);field.set(module, mockApi);
// testCompile 'net.vidageek:mirror:1.6.1'new Mirror().on(module).set().field("api").withValue(mockApi);
Mas pode usar reflection no
Android? Não é lento?
I – Testes unitários rodam na JVM. Não tem esse
problema.
II – É um teste de comportamento no emulador. Alguns
segundos de setup são aceitáveis.
Code Coverage
dados sobre quanto do código está sendo testado
Jacoco
Plugin no Gradle
Requer algumas configurações no arquivo de build
Notas Finais
algumas dicas para os navegantes de primeira viagem
Test Butler
Biblioteca + APK para garantir uma execução mais tranquila dos testes no emulador
Permite controlar animações, rede, etc.
https://github.com/linkedin/test-butler
Dicas Finais
Testes Unitários != TDD
Cuidado com os Mocks:
- Não faça mock de tudo- Não faça mock de value objects (POJOs)- Não faça mock de tipos que você não tem- Mostre amor pelos seus testes <3
Dicas Finais
Não use flavors para criação de mocks!
- invasivo ao set-up do projeto- código duplicado- limita a configuração de comportamentos diferentes
para a mesma unidade de código
Links
google.github.io/android-testing-support-library
github.com/googlesamples/android-testing
blog.sqisland.com
github.com/googlesamples/android-topeka
github.com/chiuki/friendspell
github.com/concretesolutions/requestmatcher
github.com/rafaeltoledo/android-keep-testing
rafaeltoledo.nettwitter.com/_rafaeltoledo
blog.concretesolutions.com.brconcretesolutions.com.br/carreira
Rio de Janeiro – Rua São José, 90 – cj. 2121Centro – (21) 2240-2030
São Paulo - Rua Sansão Alves dos Santos, 433 4º andar - Brooklin - (11) 4119-0449