Page Object Pattern en Android

jueves, 10 de septiembre de 2015



Parge Object Pattern es una patrón de diseño que se utiliza en los test automatizados end-to-end, surgió como un patrón para testear aplicaciones web, de ahí el nombre. Pero en realidad puede aplicar a cualquier tipo de interfaz de usuario como puede ser aplicaciones móviles nativas como Android. Cuando lo utilizo en Android o iOS me gusta llamarlo Screen Object Pattern porque me parece un nombre más apropiado, pero la esencia es la misma.

En este artículo vamos a ver como aplicar este patrón en una aplicación Android.

Introducción al Patrón

Cuando creamos test funcionales o de aceptación contra una aplicación Android, lo habitúal es utilizar un framework para test automatizados como Robotium o Espresso de Google, generalmente mediante estos frameworks acatacamos a la interfaz de usuario basandonos en el tipo del control como Toolbar, TextView o también accedemos a los controles a través de su Id (R.Id.one_button).

Esto convierte a este tipo de test en frágiles ante cualquier cambio en la interfaz de usuario. ¿Que sucede si cambiamos el id de un control? ¿Y si cambiamos un Listview por un RecyclerView?, vamos a tener que modificar muchos de test.

Para solucionar este problema aparece el patrón Page Object, o Screen Object como me gusta llamarlo cuando no trabajamos con páginas, el objetivo es abstraer a los test de la forma de acceder a los controles de la pantalla. Creamos una capa de objetos entre los test y el framework de automatización de test para conseguirlo. Vamos a tener un objeto por pantalla y vamos a tener métodos o funciones para representar las acciones dentro de la pantalla, podemos tener getter que expogan la información que se encuentra en la pantalla o podemos tener métodos de validación (assertions) dentro del propio screen object.

Adaptación del diagrama de Martin Fowler al mundo Android.



Respecto donde debería estar las validaciones (assertions) hay dos vertientes, lo que piensan que los page object no deberían validar nada porque no es su responsabilidad, como opina Martin Fowler, las validaciones en este caso son responsabilidad del propio test. Y los que piensan que la validaciones deben estar dentro de los screen objects para no incumplir el principio Tell dont Ask y porque así se evita duplicación de ciertas validaciones quedando esta lógica encapsulada dentro del screen object.

Yo sinceramente todavía me encuentro esperimentando con el tema y no tengo una posición concreta de momento, y no se si la tendré. Si me he dado cuenta que según el framework parece que te invita a una cosa o a otra. Por ejemplo Espresso tiene sus propios assertions entonces lo mas sencillo es que el screen object valide encapsulando dichas assertions. Pero como os digo de momento no tengo una posición determinada al respecto.

Ejemplo

Nada mejor que un ejemplo para entender el concepto.

La aplicación sobre la que vamos a hacer pruebas es una calculadora.



Vamos a tener un test que verifica una combinacion de cálculos

Test frágiles

Primero vamos a ver como sería el test sin screen object utilizando Espresso como framework.


@Rule
public ActivityTestRule<MainActivity> mainActivityRule = new ActivityTestRule<>(MainActivity.class,true,false);

@Test
public void PressThree_PressAdd_PressFour_PressAdd_PressFive_PressAdd_PressSix_PressEqual_DisplayShouldShowEighteen(){
   mainActivityRule.launchActivity(null);

   onView(withId(R.id.three_button))
           .perform(click());

   onView(withId(R.id.add_button))
           .perform(click());

   onView(withId(R.id.four_button))
           .perform(click());

   onView(withId(R.id.add_button))
           .perform(click());

   onView(withId(R.id.five_button))
           .perform(click());

   onView(withId(R.id.add_button))
           .perform(click());

   onView(withId(R.id.six_button))
           .perform(click());

   onView(withId(R.id.equal_button))
           .perform(click());

   onView(withId(R.id.display_TextView))
           .check(matches(withText("18")));
}

Test con Screen Object

Tendremos un CalculatorScreen con acciones para iniciar el activity, pulsar botones y otro para validar el texto del display:

public class CalculatorScreen extends ScreenObject {

    @Rule
    public ActivityTestRule<MainActivity> mainActivityRule = new ActivityTestRule<>(MainActivity.class,true,false);

    public CalculatorScreen start(){
        mainActivityRule.launchActivity(null);

        return this;
    }

    public CalculatorScreen pressNumber(String number){
        onView(withText(number))
                .perform(click());

        return this;
    }

    public CalculatorScreen pressOperation(String operator){
        onView(withText(operator))
                .perform(click());

        return this;
    }

    public CalculatorScreen pressEqual(){
        onView(withId(R.id.equal_button))
                .perform(click());

        return this;
    }

    public CalculatorScreen verifyTextDisplay(String text){
        onView(withId(R.id.display_TextView))
                .check(matches(withText(text)));

        return this;
    }
}


Fijaros que todos los métodos devuelven el objeto es si mismo, proporcionando asi una fluent interface que va a permitir escribit test más legibles. Ahora el test sería asi:

    @Test
    public void PressThree_PressAdd_PressFour_PressAdd_PressFive_PressAdd_PressSix_PressEqual_DisplayShouldShowEighteen(){
        CalculatorScreen calculatorScreen = new CalculatorScreen();

        calculatorScreen
                .start()
                .pressNumber("3")
                .pressOperation("+")
                .pressNumber("4")
                .pressOperation("+")
                .pressNumber("5")
                .pressOperation("+")
                .pressNumber("6")
                .pressEqual()
                .verifyTextDisplay("18");
    }


Ahora el test tiene menos lineas de código y es mucho más legible. Si tenemos 50 tests con combinaciones de cálculos y cambiamos el id de los botones no hay que modificar 50 tests, solo CalculatorScreen y los test funcionarían.

Conclusiones

El patrón Page Object (aka Screen Object) nos permite que los test end to end no sean tan frágiles ante cambios en la interfaz de usuario o incluso a cambiar el framework que utilizamos para test funcionales.

No hay comentarios:

Publicar un comentario