Developing Frogtek

El blog del Departamento de Tecnología

Etiqueta: activity

Robolectric: TDD en Android

Llevábamos ya desde nuestros inicios buscando herramientas que nos permitieran practicar TDD para desarrollar las clases de nuestro proyectos en Android. El problema era que las Activity (clase fundamental de las aplicaciones Android) solo podían ser testeadas a través del propio entorno Android, nunca como clase independiente. Por lo tanto, los únicos tests que habíamos realizado sobre esas clases eran tests de integración, que podían tardar del orden de 4 minutos en ejecutarse por completo, lo que nos imposibilitaba el realizar un diseño guiado por tests.

Robolectric (http://pivotal.github.com/robolectric) es una herramienta que nos va a permitir lanzar tests unitarios en Android.

Su uso nos aporta varias ventajas:

  • Ejecuta tests en Android que duran segundos y no minutos, como en el caso de un Android jUnit test.
  • Su velocidad nos permite practicar TDD al crear clases en Android.
  • Podemos usar Mockito o PowerMock en conjunción con los tests de Robolectric.
  • Nos permite utilizar clases de librerías Android, como JSONObjects o JSONArrays, que en otros entornos no podríamos usar.

Como algunas clases de Android no tienen métodos para acceder al estado o las variables que contienen, la gente de Robolectric se ha sacado de la manga los Shadows (ShadowImageView, ShadowRelativeLayout, ShadowActivity, etc.), los cuales nos van a permitir realizar pruebas como esta:

@Test
public void testImagenEsCorrecta() throws Exception {
    ImageView imagenBanner = (ImageView) activity.findViewById(R.id.banner);
    ShadowImageView shadowBanner = Robolectric.shadowOf(imagenBanner);
    assertThat(shadowBanner.resourceId, equalTo(R.drawable.img_banner_grande));
}

Cómo configurar Robolectric para Eclipse:

Estos son los pasos básicos para configurar Robolectric en Eclipse. Recomiendo seguir el quick-start de la web oficial para más detalles:

  1. Crear una nueva carpeta en la raíz de nuestro proyecto que vamos a testear, llamada test.
  2. Crear un nuevo proyecto (u obtenerlo del SVN) llamada MyProjectTest.
  3. Queremos meter el código dentro de la carpeta test antes creada, así que en MyProjectTest haremos clic en propiedades y en Build Path/Source/Link Source seleccionaremos la carpeta test.
  4. Añadir al proyecto el .jar de Android.
  5. Añadir al proyecto el .jar de Robolectric (¡con dependencias!).
  6. Añadir una nueva run configuration en Run/Run configurations, de forma que elijamos Eclipse JUnit launcher y en la pestaña Arguments seleccionaremos Other/Workspace y elegiremos la carpeta raíz del proyecto que queremos testear.

Ejemplo de uso para testear una Activity:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
    private Activity activity;
    private Button pressMeButton;
    private TextView results;

    @Before
    public void setUp() throws Exception {
        activity = new MyActivity();
        activity.onCreate(null);
        pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
        results = (TextView) activity.findViewById(R.id.results_text_view);
    }

    @Test
    public void shouldUpdateResultsWhenButtonIsClicked() throws Exception {
        pressMeButton.performClick();
        String resultsText = results.getText().toString();
        assertEquals("Testing Android Rocks!", resultsText);
    }
}

De momento, Robolectric es un proyecto joven (surgió en noviembre de 2010). Esperamos que esta herramienta mantenga una cierta continuidad, para que podamos obtener el máximo rendimiento a las prestaciones que nos ofrece.

Por ahora estamos experimentando las posibilidades que nos otorga, todavía nos queda por descubrir como crear nuestros propios objetos Cursor dentro de este entorno. Seguiremos informando…

El findViewById(), y lo cargante que puede llegar a ser

Si no vienes del post introductorio, quizás te gustaría leerlo.

Segundo asalto. La aplicación sigue estando lenta, y no sabemos muy bien por qué. Ya no hay tantos onMeasure(); sólo los “necesarios”. Pero hay una nueva función que aparece en los primeros puestos de nuestro querido traceview: findViewTraversal(). ¿Y quién llama tantísimas veces a esta última? pues findViewById().
Y es que esta función, es la que enlaza un recurso de la interfaz de usuario de una aplicación, con una variable en nuestro código. O simplemente se encarga de acceder a ese recurso para cambiar una de sus propiedades.

Supongamos, pues, que tenemos un layout con una veintena de widgets entre layouts, Buttons, EditText, TextView, ImageView… Y que en un momento dado del ciclo de vida de la actividad, tenemos que acceder a uno ó varios de ellos para cambiar sus propiedades. Podemos acceder a ellos del modo:
((EditText) this.findViewById(R.id.edittext_cliente)).setOnClickListener(…);
Aunque también podemos guardar el valor que devuelve la función findViewById() en una variable, o variable miembro de la Activity para operar con ella varias veces a lo largo del citado ciclo de vida.

¿Cuál es la diferencia? y ya que estamos ¿cuál era nuestro error? Pues que cada vez que se llama a la función findViewById(), android recorre todo el árbol jerárquico del Layout que hemos definido previamente en XML, en busca del recurso al que queremos acceder. Como ya habréis averiguado, el costo de esa operación es muy alto si el control a encontrar es de los últimos en el árbol, o si hay muchos niveles de layouts anidados, o simplemente queremos cambiar el valor de una de las propiedades de ese recurso bastantes veces a lo largo del ciclo de vida de la aplicación.
Si a esto le sumamos que el cambio en una de las propiedades de un EditText (por ejemplo), afecta a cinco o seis widgets más; y a todos accedemos del modo findViewById(), estamos vendidos.

La solución para una Activity, cuyos widgets han de ser modificados con bastante asiduidad es el guardar el resultado de la función archinombrada en este post en variables miembro, a las cuales se accederá sin tener que recorrer (findViewTraversal()) todo el árbol jerárquico “n” veces, con la consiguiente pérdida de velocidad en la transición de un estado a otro de la Activity.
Eso sí: Podemos prescindir de estas variables miembro si accedemos una, y sólo una, vez al widget en cuestión.

Esto también hay que grabarlo a fuego en la mente.

Optimizar una aplicación android (Introducción)

Hacía como mes y medio que quería escribir sobre este tema, pero me habría precipitado ya que desde esa fecha hasta ahora hemos aprendido mucho más sobre la optimización de una aplicación para android; aunque aún no quedan muchos conocimientos por adquirir.

Como ya hemos contado alguna vez, desde hace casi dos años, desarrollamos nuestro producto para teléfonos; teléfonos con una resolución fija. Y durante este verano, hemos dado el salto a una tablet. Suerte que todo el núcleo de la aplicación lo teníamos bien separado en un .jar. Por lo que el 2% del tiempo lo hemos invertido en retocar un poquito el núcleo, y el 95% restante ha sido armar una interfaz de usuario muy bonita, pero que nos ha dado bastantes quebraderos de cabeza, y que voy a intentar plasmar en una serie de posts.

Tras unas cuantas iteraciones y un empujón espectacular, teníamos muy avanzada la versión para la tablet. Pero el cambio de una pantalla de 320 x 480, a una de 800 x 480 conlleva problemas de memoria y de rendimiento (con un hardware superior al del teléfono, todo iba mucho más lento). Por que no es oro todo lo que reluce. Con un buen procesador, y bastante memoria no todo va a funcionar como pensabas. Por lo menos, en android. Nos hemos rascado mucho la cabeza, hemos sufrido, pero finalmente hemos aprendido muchas cosas; sobretodo de nuestros errores.

Es por ello, que nos gustaría compartir con vosotros los siguientes temas:

A medida que vayamos redactando la serie de posts, se actualizarán los puntos con enlaces a las URLs.