No mezclemos código de testing y código de producción

Es habitual en cualquier tecnología que los grandes como Apple, Google o Microsoft nos intenten ayudar a los desarrolladores dándonos utilidades mágicas que nos solucionan un problema dado pero que nos meten en otro problema incluso mayor muchas veces.

Es importante aprender a identificar malos olores en el código que nos indiquen que una solución propuesta puede ser una mala idea, aunque sea una empresa importante quien nos expone en su framework dicha funcionalidad.

No mezclar responsabilidades

Suele ser mala práctica mezclar responsabilidades.

Es cierto que en ciertas situaciones donde no tenemos un conocimiento de la funcionalidad de una aplicación puede llegar a resultar complicado identificar estas responsabilidades y es fácil que cometamos este error.

Sin embargo hay otras situaciones donde da igual la tecnología y la aplicación donde las responsabilidades si están claras. Todos sabemos cual es la responsabilidad del código de producción y del código de testing.

Mezclar código de testing y código de producción es una mala práctica

Acoplar ambos códigos es una mala idea que a la larga nos traerá problemas.

Es una fuente de problemas porque el desarrollador que esta tocando el código de producción no tiene porque conocer que cierta parte del código en realidad corresponde a testing o se hace para que funcionen ciertos test.

Protegernos de los frameworks

Algo que primero se fue teniendo claro en backend y que poco a poco se va implantando en mobile es que nos tenemos que proteger de los frameworks.

Ya de por si es peligroso depender de frameworks pero si encima desde código de producción dependemos de un framework de testing el problema es aun mayor porque estamos mezclando dos mundos que a priori esta bastante claro que son dos cosas distintas.

Android y Espresso

Vamos a ver un caso donde podemos llegar a cometer esta mala práctica.

El escenario que vamos a ver es en Android utilizando Espresso como framework de testing para simular acciones de un usuario sobre nuestra aplicación.

El problema a resolver

Primero vamos a poner algo de contexto.

Espresso sabe esperar a que terminen los eventos de UI y las tareas que están siendo ejecutadas en un AsyncTask antes de moverse a la siguiente instrucción a ejecutar desde nuestro test.

El problema surge cuando realizamos tareas en background utilizando alternativas como Threads, ThreadPool, o librerías como RxJava y Priority Job Queue.

Cuando utilizamos están alternativas Espresso no sabe que se están realizando tareas en background y lo normal es que nos falle en cuanto el hilo principal de UI se queda en reposo si no encuentra cierta vista que esperamos sea visible cuando termine la tarea en background.

Solución acoplada

Para solucionar el problema, Espresso tiene una librería espresso-idling-resource. Esta librería tiene un interface IdlingResource que simplificando sirve para indicar a Espresso que la aplicación esta ocupada haciendo una tarea en background.

Espresso ofrece una clase CountingIdlingResource que implementa IdlingResource y consiste en un contador de tareas pendientes que aumentamos mediante el método increment y disminuimos mediante el método decrement.

Cuando el contador de tareas pendientes de CountingIdlingResource se queda a 0, este avisa a Espresso de que ya no esta ocupada nuestra aplicación.

Espresso ofrece este ejemplo de uso en su documentación:

public interface FooServer {
     public Foo newFoo();
     public void updateFoo(Foo foo);
   }

   public DecoratedFooServer implements FooServer {
     private final FooServer realFooServer;
     private final CountingIdlingResource fooServerIdlingResource;

     public DecoratedFooServer(FooServer realFooServer,
         CountingIdlingResource fooServerIdlingResource) {
       this.realFooServer = checkNotNull(realFooServer);
       this.fooServerIdlingResource = checkNotNull(fooServerIdlingResource);
     }

     public Foo newFoo() {
       fooServerIdlingResource.increment();
       try {
         return realFooServer.newFoo();
       } finally {
         fooServerIdlingResource.decrement();
       }
     }

     public void updateFoo(Foo foo) {
       fooServerIdlingResource.increment();
       try {
         realFooServer.updateFoo(foo);
       } finally {
         fooServerIdlingResource.decrement();
       }
     }
   }

¿Donde esta el problema? el problema es que si utilizamos CountingIdlingResource, necesitamos utilizarlo desde el código de producción y allí donde se inicia una tarea en background aumentemos el contador de tareas pendientes y al finalizar disminuyamos este contador.

En este caso utiliza un decorador que minimiza el problema de tocar el código de producción directamente pero no evita que se sigue mezclando código de testing y código de producción.

Es fácil caer en la tentación de utilizar CountingIdlingResource porque es lo más sencillo y lo que ofrece Espresso.

Si somos capaces de ver el problema de mezclar responsabilidades intentaremos buscar una solución alternativa.

Una solución sin acoplamiento

Probablemente existan otras soluciones pero os voy a explicar una que suelo utilizar yo en mis proyectos y donde evito mezclar la responsabilidad del los test y del código de producción.

Generalmente en las aplicaciones cuando se esta realizando una tarea en background de alguna forma avisamos al usuario de esta situación.

Es habitual mostrar un ProgressBar que indique al usuario que la aplicación esta ocupada, ¿Porque no utilizar la visibilidad de este ProgressBar para indicar lo mismo a Espresso?, vamos a ver como sería.

Nos tendríamos que crear una clase que implemente IdlingResource que se llame ProgressBarIdlingResource y que en función de si un ProgressBar esta visible o no, avise a Espresso de que nuestra aplicación esta ocupada o ha finalizado y esta en reposo.

public class ProgressbarIdlingResource implements IdlingResource{

    private Activity activity;
    private ResourceCallback callback;

    public ProgressbarIdlingResource(Activity activity){
        this.activity = activity;
    }

    @Override
    public String getName() {
        return "ProgressbarIdlingResource";
    }

    @Override
    public boolean isIdleNow() {
        View view = activity.findViewById(R.id.pb_loading);

        boolean loadingHide = !view.isShown();

        if (loadingHide)
            callback.onTransitionToIdle();

        return loadingHide;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback callback) {
        this.callback = callback;
    }
}

Curso y artículos relacionados

Conclusiones

Hemos visto que es una mala práctica mezclar código de testing y código de producción.

A veces es fácil caer en este error, sobre todo si los frameworks de empresas importantes son los que nos exponen funcionalidades que nos incitan a ello.

Es importante aprender a descubrir olores en el código que nos pueden meter en problemas.