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 test
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.1.2'
// somente androidTest
androidTestCompile '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 Android
assertThat(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 comportamento
System.out.println(mockedList.get(999));
Framework para a criação de testes Instrumentados no Android
Espresso
AndroidJUnitRunner
JUnit4 Rules
UI Automator
Android Testing
Support 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 Testing
Support 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 Testing
Support 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
@Test
public 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
// O
Calculator c = new Calculator();
c.setFirstNumber(1);
c.setSecondNumber(2);
c.setOperation(Calculador.SUM);
// C
c.performOperation();
// A
assertThat(c.getResult(), is(3));
Código difícil de testar
testes 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 Externas
como 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 mockito
StackApi api = mock(StackApi.class);
when(api.getUsers(anyInt()).thenReturn(createMockedResponse());
// Mockando callbacks
ArgumentCaptor<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
@Mock
StackApi api;
when(api.getUsers(anyInt()).thenReturn(createMockedResponse);
// Mockando callbacks
@Captor
Callback<ApiCollection<User>>> captor;
verify(api.getUsersAsync(anyInt(), captor);
captor.getValue().onSuccess(createMockedCall(), createMockedResponse());
Mock Server
// Mockando a API com o mockito
MockWebServer 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 Singleton
ApiModule module = ApiModule.getInstance();
Field field = module.getClass().getDeclaredField("api");
field.setAccessible(true);
field.set(module, mockApi);
Reflection
// Mudamos o valor do Singleton
ApiModule 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.net
twitter.com/_rafaeltoledo
blog.concretesolutions.com.br
concretesolutions.com.br/carreira
Rio de Janeiro – Rua São José, 90 – cj. 2121
Centro – (21) 2240-2030
São Paulo - Rua Sansão Alves dos Santos, 433
4º andar - Brooklin - (11) 4119-0449

TDC2016POA | Trilha Android - Testes no Android

  • 1.
    Conceitos, práticas emotivações Testes no Android
  • 2.
    Eu Chapter Lead deAndroid www.rafaeltoledo.net twitter.com/_rafaeltoledo github.com/rafaeltoledo blog.concretesolutions.com.br
  • 3.
    Por que escreverTestes? o dilema do programador mobile
  • 4.
    Garantir que algofunciona da forma como deveria Documentação de comportamento de um sistema Garantir que uma mudança não quebra outras partes do app
  • 7.
    A pirâmide refere-seao 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
  • 8.
    Por que tenho2 pastas de Testes? o que são as pastas test e androidTest no meu projeto?
  • 9.
    Testes unitários /funcionais instrumentados, que necessitam das classes do Android para a execução. São executados em emuladores ou devices reais androidTest
  • 10.
    Testes unitários executadosna JVM (máquina local) Componentes externos geralmente são mockados (como as classes do Android)* test
  • 11.
    Testes unitários executadosna JVM (máquina local) Componentes externos geralmente são mockados (como as classes do Android)* Robolectric
  • 12.
    escopos de dependências //somente test testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.1.2' // somente androidTest androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5'
  • 13.
    Bibliotecas e Frameworks quempoderá nos ajudar?
  • 14.
    Framework para criaçãode 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); } }
  • 15.
    Biblioteca para acriaçã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
  • 16.
    Biblioteca para acriaçã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 Android assertThat(view).isGone(); AssertJ
  • 17.
    Biblioteca para acriação de mocks List mockedList = mock(List.class); mockedList.add("one"); mockedList.clear(); verify(mockedList).add("one"); verify(mockedList).clear();
  • 18.
    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 comportamento System.out.println(mockedList.get(999));
  • 19.
    Framework para acriação de testes Instrumentados no Android Espresso AndroidJUnitRunner JUnit4 Rules UI Automator Android Testing Support Library
  • 20.
    Espresso Biblioteca para aescrita 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 Testing Support Library
  • 21.
    AndroidJUnitRunner Suporte ao JUnit4, 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 Testing Support Library
  • 22.
    “Robolectric é umframework 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
  • 23.
    @Test public 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
  • 24.
    Request Matcher Biblioteca open-sourcepara 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
  • 25.
    Organização dos testes porqueteste também é código
  • 26.
    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
  • 27.
    Organização OCA Organizar: set-updos 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
  • 28.
    Organização OCA // O Calculatorc = new Calculator(); c.setFirstNumber(1); c.setSecondNumber(2); c.setOperation(Calculador.SUM); // C c.performOperation(); // A assertThat(c.getResult(), is(3));
  • 29.
    Código difícil detestar testes podem denunciar problemas no design de classes
  • 30.
    Design de classes Calculatorc = new Calculator(); c.setFirstNumber(1); c.setSecondNumber(2); c.setOperation(Calculador.SUM); c.performOperation(); assertThat(c.getResult(), is(3));
  • 31.
    Design de classes Calculatorc = new Calculator.Builder() .firstNumber(1) .secondNumber(2) .operation(Calculador.SUM) .build(); c.performOperation(); assertThat(c.getResult(), is(3));
  • 32.
    Por onde começar? ok,conheço as ferramentas... mas o que eu testo?
  • 33.
    Nossa cobaia seráum app que consome a API do StackOverflow e lista os usuários com a maior reputação no site
  • 34.
    Consumo de APIcom Retrofit RecyclerView com endless scroll Salvando dados na mudança de orientação!
  • 35.
    app/build.gradle defaultConfig { applicationId 'net.rafaeltoledo.tests' minSdkVersion16 targetSdkVersion 23 versionCode 1 versionName '0.0.1' testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' }
  • 36.
  • 37.
    app/build.gradle @RunWith(AndroidJUnit4.class) public class HomeActivityTest{ @Rule public ActivityTestRule<HomeActivity> activityRule = new ActivityTestRule<>(HomeActivity.class); ... }
  • 38.
    androidTest/. . ./HomeActivityTest.java public class HomeActivityTest { @Test public void checkIfRecyclerViewIsLoading() { // ... } }
  • 39.
    androidTest/. . ./HomeActivityTest.java public class HomeActivityTest { @Test public void checkIfRecyclerViewIsLoading() { // êpa! Calma aí... } }
  • 40.
    Testes e DependênciasExternas como fazemos com a API? como fazemos com hardware? Como fazemos pra testar?
  • 46.
    The FIRST Thingsfor 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
  • 47.
    The FIRST Thingsfor 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.
  • 48.
  • 49.
    Abordagens de Mock MockObjects: 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)
  • 50.
    Mock objects // Mockandoa API com o mockito StackApi api = mock(StackApi.class); when(api.getUsers(anyInt()).thenReturn(createMockedResponse()); // Mockando callbacks ArgumentCaptor<Callback<ApiCollection<User>>> captor = forClass(Callback.class); verify(api.getUsersAsync(anyInt(), captor); captor.getValue().onSuccess(createMockedCall(), createMockedResponse());
  • 51.
    Mock objects @RunWith(MockitoTestRunner.class) // Mockandoa API com o mockito @Mock StackApi api; when(api.getUsers(anyInt()).thenReturn(createMockedResponse); // Mockando callbacks @Captor Callback<ApiCollection<User>>> captor; verify(api.getUsersAsync(anyInt(), captor); captor.getValue().onSuccess(createMockedCall(), createMockedResponse());
  • 52.
    Mock Server // Mockandoa API com o mockito MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse() .setBody(json) // string! .addHeader("Header", "value") .setResponseCode(200));
  • 53.
    Mas... Como fazer meuapp utilizar esses objetos?
  • 54.
    Mas... Como fazer meuapp utilizar esses objetos? 1. DI / Setter
  • 55.
    Mas... Como fazer meuapp utilizar esses objetos? 1. DI / Setter 2. Reflection
  • 56.
    DI / Setter //API Mockada que criamos :) apiSingleton.setApi(mockApiObject);
  • 57.
    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
  • 58.
    DI / Setter publicclass ApiSingleton { // ... @VisibleForTesting public void setApi(MyApiInterface api) { this.api = api; } }
  • 59.
    DI / Setter publicclass ApiSingleton { // ... @VisibleForTesting public void setApi(MyApiInterface api) { this.api = api; } } PS: Dagger é uma boa saída pra fazer essa troca
  • 60.
    Reflection // Mudamos ovalor do Singleton ApiModule module = ApiModule.getInstance(); Field field = module.getClass().getDeclaredField("api"); field.setAccessible(true); field.set(module, mockApi);
  • 61.
    Reflection // Mudamos ovalor do Singleton ApiModule 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);
  • 62.
    Mas pode usar reflectionno Android? Não é lento?
  • 63.
    I – Testesunitários rodam na JVM. Não tem esse problema. II – É um teste de comportamento no emulador. Alguns segundos de setup são aceitáveis.
  • 64.
    Code Coverage dados sobrequanto do código está sendo testado
  • 65.
    Jacoco Plugin no Gradle Requeralgumas configurações no arquivo de build
  • 66.
    Notas Finais algumas dicaspara os navegantes de primeira viagem
  • 67.
    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
  • 68.
    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
  • 69.
    Dicas Finais Não useflavors 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
  • 70.
  • 71.
    rafaeltoledo.net twitter.com/_rafaeltoledo blog.concretesolutions.com.br concretesolutions.com.br/carreira Rio de Janeiro– Rua São José, 90 – cj. 2121 Centro – (21) 2240-2030 São Paulo - Rua Sansão Alves dos Santos, 433 4º andar - Brooklin - (11) 4119-0449