Testeas o k ase??

Post on 22-May-2015

716 views 2 download

description

Slides usadas en la charla Testeas o k ase?? del Codemotion Spain 2013. En la sesión se pretende explicar la importancia del testing en dispositivos móviles, así como animar a los desarrolladores a que implementen tests unitarios en sus aplicaciones, concretamente usando el framework Robolectric para Android. Se explicará el funcionamiento de la herramienta, mostrando por qué es tan potente junto con las posibilidades que nos ofrece y, finalmente, se enseñará un ejemplo práctico detallando lo fácil que es tanto configurarlo como empezar a escribir tests.

Transcript of Testeas o k ase??

@Guardiola31337 - pguardiola.com

Pablo GuardiolaTesteas o k ase??

@Guardiola31337 - pguardiola.comPablo GuardiolaAgenda

‣ Importancia del testing sobre dispositivos móviles

‣ Robolectric

‣ Demo

‣ Número de apps Android en el market: 866033‣ Porcentaje de apps de baja calidad: 23 %

@Guardiola31337 - pguardiola.comPablo GuardiolaNumerosas apps en los markets = Competencia brutal

‣ Porcentaje de apps con menos de 3 ratings: 43.9 %‣ Porcentaje de apps entre 0 y 4 estrellas: 65.22 %

@Guardiola31337 - pguardiola.comPablo GuardiolaNumerosas apps en los markets = Competencia brutal

‣ No cabrees a tus usuarios

@Guardiola31337 - pguardiola.comPablo Guardiola¿El código hace exactamente lo que quieres?

@Guardiola31337 - pguardiola.comPablo Guardiola¿El código hace exactamente lo que quieres?

‣ Testing manual = mucho tiempo

@Guardiola31337 -pguardiola.comPablo Guardiola¿Podemos reducir el tiempo dedicado a testing usando tests automáticos?

‣ Testing automático

@Guardiola31337 - pguardiola.comPablo Guardiola¿Podemos reducir el tiempo dedicado a testing usando tests automáticos?

‣ ¡Cuidado con las actualizaciones!

@Guardiola31337 -pguardiola.comPablo GuardiolaVivimos en un mundo ágil

@Guardiola31337 -pguardiola.comPablo GuardiolaTesteas o k ase??

‣ Incrementa el nivel de confianza que se tiene del código

‣ Hacer testing “obliga” a escribir código modular

‣ Se desarrolla un producto de calidad

@Guardiola31337 - pguardiola.comPablo GuardiolaVentajas de hacer testing

‣ Go, go, go!

@Guardiola31337 - pguardiola.comPablo GuardiolaAndroid testing

‣ JUnit

@Guardiola31337 - pguardiola.comPablo GuardiolaAndroid testing

‣ Los métodos de android.jar devuelven Stub!

‣ Android testing framework

@Guardiola31337 - pguardiola.comPablo GuardiolaOtras opciones

‣ Tests deben ejecutarse en un terminal o emulador

‣ Separar código Java y Android

‣ POJO’s

‣ Tests instrumentales

‣ Mocking framework

‣ ¡¡¡NO HACER!!!

@Guardiola31337 - pguardiola.comPablo GuardiolaOtras opciones

@Guardiola31337 - pguardiola.comPablo GuardiolaOtras opciones

‣ Entonces, ¿qué?

@Guardiola31337 - pguardiola.comPablo Guardiola¿Por qué usar Robolectric?

‣ Es rápido

‣ Interacción con las clases de Android

‣ Incluye HTTP/API testing

‣ Permite invocar métodos privados

‣ Muy fácil de integrar con otros frameworks

@Guardiola31337 - pguardiola.comPablo GuardiolaAdemás, Robolectric te permite...

‣ Usar JUnit 4

‣ Iteraciones rápidas

‣ Permite aplicar TDD en el desarrollo

‣ Verificar la lógica y el comportamiento frente a la implementación

‣ Shadows

@Guardiola31337 - pguardiola.comPablo Guardiola¿Cómo funciona?

@Guardiola31337 - pguardiola.comPablo Guardiola¿Cómo funciona?

@Guardiola31337 - pguardiola.comPablo Guardiola¿Cómo funciona?

@Guardiola31337 - pguardiola.comPablo Guardiola¿Cómo funciona?

‣Robolectric actúa como un proxy

‣ Se debe indicar con una anotación qué clase es la encargada de realizar el test

@Guardiola31337 - pguardiola.comPablo GuardiolaShow me the code!

@RunWith(RobolectricTestRunner.class) // <== REQUIRED for Robolectric!public class HomeActivityTest { @Test public void shouldHaveAButtonThatSaysPressMe() throws Exception { // test code here }}

‣ En ocasiones Robolectric está oculto...

@Guardiola31337 - pguardiola.comPablo GuardiolaShow me the code!

@RunWith(RobolectricTestRunner.class)public class HomeActivityTest { @Test public void shouldHaveHappySmiles() throws Exception { String appName = new MyActivity() .getResources().getString(R.string.app_name); assertThat(appName, equalTo("SampleRobolectric")); }}

‣ ¡Escondido! Pero haciendo su trabajo \o/

‣ Métodos para acceder al estado de los objetos Android

@Guardiola31337 - pguardiola.comPablo GuardiolaShadows

<ImageView android:id="@+id/pivotal_logo" android:layout_width="fill_parent" android:layout_height="wrap_content" android:src="@drawable/pivotallabs_logo" android:layout_marginBottom="10dip" > @Testpublic void shouldHaveALogo() throws Exception { ImageView pivotalLogo = (ImageView)activity.findViewById(R.id.pivotal_logo); ShadowImageView shadowPivotalLogo = Robolectric.shadowOf(pivotalLogo); assertThat(shadowPivotalLogo.resourceId, equalTo(R.drawable.pivotallabs_logo));}

‣Acceso a estado y variables, de otro modo sería imposible

@Guardiola31337 - pguardiola.comPablo GuardiolaHTTP testing

@RunWith(RobolectricTestRunner.class)public class HttpTest { @Test public void testGet_FormsCorrectRequest_noBasicAuth() throws Exception { Robolectric.addPendingHttpResponse(200, "OK"); new Http().get("www.codemotion.es",            Maps.<String, String>newHashMap(),            null,            null); assertThat(((HttpUriRequest) Robolectric.getSentHttpRequest(0)).getURI(),            equalTo(URI.create("www.codemotion.es"))); }}

@Guardiola31337 - pguardiola.comPablo GuardiolaHTTP testing

‣ Robolectric hace de mock server, necesita saber cómo responder a la llamada para evitar RuntimeException

‣ Respuesta por defecto compartida

Robolectric.addPendingHttpResponse(200, "OK");

@RunWith(RobolectricTestRunner.class)public class HttpTest { @Before public void setup() { Robolectric.addPendingHttpResponse(200, "OK"); } ...}

@Guardiola31337 - pguardiola.comPablo GuardiolaHTTP testing

‣ Otros ejemplos

@Test public void shouldReturnCorrectResponse() throws Exception { Robolectric .addPendingHttpResponse(666, "it's all cool"); Http.Response response = http.get("www.example.com", new HashMap<String, String>(), null, null);    assertThat(fromStream(response.getResponseBody()), equalTo("it's all cool"));    assertThat(response.getStatusCode(), equalTo(666)); }}

‣ Métodos correctos, cabeceras...

@Guardiola31337 - pguardiola.comPablo GuardiolaExtendiendo Robolectric

‣ Robolectric no tiene funcionalidad para todo‣ Robolectric permite extender las clases Shadow

@Implements@ImplementationRobolectric.getDefaultShadowClasses()__constructo__@RealObject

@Guardiola31337 - pguardiola.comPablo GuardiolaInconvenientes

‣ No hay clases Shadow para todo‣ No todo se comporta del mismo modo que en la DVM‣ Android no es Java

@Guardiola31337 - pguardiola.comPablo Guardiola¿Por qué no usar directamente el código Android?

‣ Difícil conseguir los .jar reales‣ Difícil trabajar con código nativo‣ Difícil dejar de usar las clases Shadow

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

2.0

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ Uso del SDK de Android

‣ No más excepciones Stub!

‣ Objetivo eliminar el 99% de las clases Shadow

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ El paquete de soporte no usa clases Shadow

‣ Fragments, action bar, loaders usan su implementación real

‣ Tests tendrán mismo comportamiento que en los dispositivos

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ Se pueden testear estilos y temas

‣ Acceso a los recursos del sistema

‣ Soporte ActionBarSherlock

‣ Mejoras de rendimiento y corrección de bugs

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ Testing bajo diferentes configuraciones del dispositivo

@Test @Config("en")public void shouldGiveEnglishResponses() throws Exception { assertThat(activity.findViewById(R.id.message)) .hasText("Hello!"); }}

@Test @Config("fr")public void shouldGiveFrenchResponses throws Exception { assertThat(activity.findViewById(R.id.message)) .hasText("Bonjour!"); }}

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ ActivityController mayor control sobre el ciclo de vida

FooActivity foo = Robolectric.buildActivity(FooActivity.class)        .create().start().resume().get();

ActivityController<FooActivity> fooController = Robolectric.buildActivity(FooActivity.class).create();FooActivity foo = fooController.get();// Ensure the user was injected.assertThat(foo.user).isNotNull();// Log the user out.foo.user.expireSession();fooController.start().resume();// Ensure the activity is now finishing.assertThat(foo).isFinishing();

@Guardiola31337 - pguardiola.comPablo GuardiolaRobolectric 2.0

‣ El uso del SDK de Android está aumentando considerablemente

‣ Todo el mundo puede contribuir:

‣ https://github.com/robolectric/robolectric‣ http://robolectric.org/‣ http://robolectric.blogspot.com.es/

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

Project name: SampleRobolectricPackage name: com.pguardiola.samplerobolectric

Activity name: MyActivityAdd Framework Support -> Maven

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

<groupId>com.pguardiola.samplerobolectric</groupId> <artifactId>SampleRobolectric</artifactId> <version>1.0-SNAPSHOT</version> <packaging>apk</packaging> <name>My Sample App</name>

<dependencies> <dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>4.1.1.4</version> <scope>provided</scope> </dependency>

<!-- Make sure this is below the android dependencies --> <dependency> <groupId>org.robolectric</groupId> <artifactId>robolectric</artifactId> <version>2.1.1</version> <scope>test</scope> </dependency>

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>

<build> <finalName>${project.artifactId}</finalName>

<plugins> <plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <version>3.6.1</version> <configuration> <sdk> <!-- platform or api level (api level 16 = platform 4.1)--> <platform>18</platform> </sdk> <undeployBeforeDeploy>true</undeployBeforeDeploy> </configuration> <extensions>true</extensions> </plugin> </plugins> </build>

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

‣ Alt+Enter -> Create Test -> JUnit4

@RunWith(RobolectricTestRunner.class)public class MyActivityTest { @Test public void shouldHaveHappySmiles() throws Exception { String appName = new MyActivity().getResources() .getString(R.string.app_name); assertThat(appName, equalTo("SampleRobolectric")); }}

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

‣ Imports

import org.junit.Test;import org.junit.runner.RunWith;import org.robolectric.RobolectricTestRunner;import static org.hamcrest.CoreMatchers.equalTo;

import static org.junit.Assert.assertThat;

@Guardiola31337 - pguardiola.comPablo GuardiolaDemo

Run -> MyActivityTest

MyActivityTest fails

SampleRobolectric pass

@Guardiola31337 - pguardiola.comPablo Guardiola¿Preguntas?

@Guardiola31337 - pguardiola.comPablo GuardiolaGracias

Happy testing!!Pablo Guardiola

@Guardiola31337