TDC2016POA | Trilha Android - Testes no Android

Post on 13-Apr-2017

143 views 0 download

Transcript of TDC2016POA | Trilha Android - Testes no Android

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