Una historia de doblaje

Estándar

Las pruebas de software son fundamentales para “garantizar” el correcto funcionamiento del mismo. Existen diferentes tipos de pruebas que se realizan a un software, cada una con diferentes objetivos como las pruebas unitarias, pruebas de integración, pruebas de carga, pruebas de seguridad, pruebas de regresión, entre otros. En el caso particular de las pruebas unitarias, se busca probar los componentes del sistema en aislamiento sin que haya interacción con otro componente del sistema, para poder obtener feedback de manera rápida. El problema es que en la mayoría de las casos los componentes interactúan con otras partes del sistema u otros sistemas en sí, haciendo difícil probar el componente de manera aislada.

¿Cómo se prueba en aislamiento un código que interactúa con otros componentes?. Para esto se hace uso de técnicas que permitan reemplazar estos componentes por “Dobles” que simulen el comportamiento esperado  de ese componente en particular para el escenario que se esta probando.

Me he tomado la libertad que traducir un artículo del gran Robert Martin, mejor conocido como El tío Bob (“Uncle Bob”. Autor de varios libros, entre ellos, la serie “Clean coder). En el artículo, Robert presenta un dialogo donde trata de explicar los diferentes tipos de Dobles de Pruebas que se usan, y algunas características de cada uno. Además se aclara un poco la terminología en relación a estos objetos.

He traducido el artículo de la manera que me ha parecido que conserva el sentido. Noten, por favor, que no he traducido muchos términos ya que considero que en lugar de aclarar, pueden confundir ya que muchos de ellos no tienen traducción directa, o la traducción literal no transmite correctamente la intención que se pretende en el texto original. Asimismo, los que han leído mis otros artículos pueden darse cuenta que normalmente no traduzco términos en inglés, ya que de esta manera pueden aprender los términos en el idioma original con el que se usan a lo largo del mundo (excepto, seguramente, en España, donde traducen todo de manera literal 🙁 ).

El artículo original lo pueden encontrar en este enlace.

The Little Mocker. por Robert C. Martin.

¿Qué es esto?

interface Authorizer {
  public Boolean authorize(String username, String password);
}

Una interfaz.

Entonces, ¿Qué es esto?

public class DummyAuthorizer implements Authorizer {
  public Boolean authorize(String username, String password) {
    return null;
  }
}

Eso es un Dummy.

Y, ¿qué se hace con un Dummy?

Lo pasas a algo cuando no te interesa como se va a usar.

¿Por ejemplo?

Como parte de una prueba. Cuando debes pasar un argumento, pero tú sabes que el argumento nunca será usado.

¿Puedes mostrar un ejemplo?

Seguro.

public class System {
    public System(Authorizer authorizer) {
        this.authorizer = authorizer;
    }

    public int loginCount() {
        //returns number of logged in users.
    }
  }

  @Test
  public void newlyCreatedSystem_hasNoLoggedInUsers() {
    System system = new System(new DummyAuthorizer());
    assertThat(system.loginCount(), is(0));
  }

Ya veo. Para poder construir System, se debe pasar como argumento un Authorizer al constructor, pero el método authorize de ese objeto nunca se llamará en esta prueba.

Exacto.

¿Y el hecho de que el método authorize del DummyAuthorizer retorna un valor null no es un error?

De hecho, no. Eso es lo mejor que puede retornar un objeto Dummy.

¿Por qué?

Porque si alguien trata de usar ese objeto Dummy, tendrían un NullPointerException.

Ah, y no quieres que se use el objeto Dummy.

¡Correcto!, es de pruebas únicamente.

Pero, ¿eso no es un Mock?. Pensé que los objetos de pruebas se llamaban Mocks.

Si, así es. Pero Mock es un argot.

¿Argot?

Si, la palabra “mock” a veces se usa informalmente para referirse a toda una familia de objetos que se usan en las pruebas.

¿Existe algún nombre formal para los objetos de pruebas?

Si, se llaman Dobles de Pruebas (“Test Doubles”).

Te refieres como a los dobles de riesgo (Stunt Doubles) de las películas?

Exactamente

Entonces, ¿la palabra “mock” es sólo un argot coloquial?

No, también tiene un significado formal; pero cuando se habla informalmente, la palabra “mock” es sinónimo de “Test Double”.

¿Por qué tenemos dos palabras?. ¿Por qué no usamos simplemente “Test Double” en lugar de “mock”?

Historia

¿Historia?

Si, hace mucho tiempo personas muy inteligentes escribieron una publicación que introdujo y definieron el termino Objeto Mock. Muchas otras personas lo leyeron y empezaron a usar ese término. Otras personas que no lo leyeron pero escucharon el término, empezaron a usarlo con un significado más amplio. Incluso convirtieron la palabra en un verbo.

 

Nota: aquí he omitido algunas líneas de la conversación porque al traducirlo directamente al español, pierden un poco el sentido. Sin embargo, no es esencial para entender el resto. Si quieren leerlo completo, puede ver la publicación en el idioma original.

 

Ok, pero cuando tenemos que hablar de una manera precisa…

Debes usar el lenguaje formal. Sí.

Entonces, ¿Qué es un “Mock”?

Antes de eso, deberíamos ver los otros tipos de “Test Doubles”.

¿Como cuales?

Veamos a los “Stubs”.

¿Qué es un stub?

Esto, es un stub:

public class AcceptingAuthorizerStub implements Authorizer {
  public Boolean authorize(String username, String password) {
    return true;
  }
}

Retorna true.

Correcto.

¿Por qué?

Bueno, supón que quieres probar una parte de tu sistema que requiere que hayas iniciado sesión.

Simplemente iniciaría sesión.

Pero ya sabes que el Inicio de Sesión funciona correctamente. Lo has probado de diferentes maneras. ¿Por qué probarlo nuevamente?

Porque, ¿es fácil?

Pero toma tiempo y requiere cierta configuración. Y si hay un bug en el inicio de Sesión, tus pruebas se romperían. Y, a final de cuentas, estas acoplando innecesariamente.

Hmmm. Bueno, por el bien de la conversación, digamos que estoy de acuerdo. ¿Y ahora?

Simplemente inyectas AcceptingAuthorizerStub en tu sistema para esa prueba.

Y va a autorizar al usuario sin preguntar.

Correcto.

Y si quieres probar la parte del sistema que maneja los usuario no autorizados, ¿Puedo usar un “Stub” que retorne falso?

Correcto, nuevamente.

OK, ¿Qué más hay?

Esta esto:

public class AcceptingAuthorizerSpy implements Authorizer {
  public boolean authorizeWasCalled = false;

  public Boolean authorize(String username, String password) {
    authorizeWasCalled = true;
    return true;
  }
}

Supongo que a eso se le llama “Spy”.

Correcto.

¿Para que se usa?

Se usa cuando quieres asegurarte que el método authorize fue invocado por tu sistema.

Ah, ya. En mis pruebas lo inyectaría como un Stub, pero luego al final de mis pruebas verifico la variable authorizerWasCalled para asegurar que mi sistema, efectivamente, llamó a authorize.

Absolutamente.

Entonces, un “Spy” espía al invocador. Me imagino que puede registrar todo tipo de cosas.

Así es. Por ejemplo, puede contar el número de invocaciones.

Si, o podría mantener una lista de los argumentos pasados cada vez.

Si. Puedes usar los “Spy” para mirar dentro del funcionamiento de los algoritmos que estas probando.

Eso suena a acoplamiento

¡Lo es!. Hay que ser cuidadosos. Entre más espíes, más acoplado estarán tus pruebas a las implementaciones de tu sistema. Y eso conlleva a pruebas frágiles.

¿Qué son pruebas frágiles?

Una prueba que se rompe por razones que no debería.

Bueno, pero si cambias el código del sistema, algunas pruebas se van a romper.

Si, pero las pruebas bien diseñadas minimizan ese efecto. Los espías van en contra de ese diseño.

OK, entiendo. ¿Qué otros tipos de “Test Doubles” existen?

Dos más. Este es el primero:

public class AcceptingAuthorizerVerificationMock implements Authorizer {
  public boolean authorizeWasCalled = false;

  public Boolean authorize(String username, String password) {
    authorizeWasCalled = true;
    return true;
  }

  public boolean verify() {
    return authorizedWasCalled;
  }
}

Y, claro, este es un “mock”.

Un Mock, de verdad. Sí.

¿De verdad?

Si, este es un objeto “mock” formal de acuerdo a la definición original de la palabra.

Ya veo. Y parece que moviste la aserción de la prueba hacia el método verify del, ahm, mock verdadero.

Correcto. Los Mocks saben que están probando algo.

¿Eso es todo?. ¿Sólo hay que poner la aserción dentro del mock?

No exactamente. Sí, la aserción va dentro del mock. Sin embargo, lo que el mock esta probando realmente es comportamiento.

¿Comportamiento?

Sí. Un mock no esta interesado en los valores de retorno de las funciones. Esta más interesado en cuales funciones fueron invocadas, con qué argumentos, cuando, y que tan frecuente.

Entonces, un mock es siempre un “Spy”?

Si. Un mock espía el comportamiento del modulo que se esta probando. Y el mock sabe el comportamiento esperado.

Hmmm. Mover el comportamiento esperado dentro del mock suena a acoplamiento.

Lo es.

Entonces, ¿Por qué hacerlo?

Hace mucho más fácil escribir una herramienta de bosquejo (mocking tool).

¿Herramienta de bosquejo?

Si, como JMock, o EasyMock, o Mockito. Estas herramientas de permiten construir objetos mocks al vuelo.

Suena complicado.

No lo es. aquí esta la publicación famosa de Martin Fowler que lo explica bien.

Y también hay un libro, ¿no?

Si. Growing Object Oriented Software, Guided by Tests es un gran libro acerca de una filosofía de diseño popular guiado por mocks.

OK, ¿ya terminamos?. Dijiste que aun había otro tipo de Test Double.

Si, uno más. Fakes.

public class AcceptingAuthorizerFake implements Authorizer {
      public Boolean authorize(String username, String password) {
        return username.equals("Bob");
      }
  }

OK, se ve raro. Cualquiera que se llame “Bob” estará autorizado.

Correcto. Un Fake tiene comportamiento de negocio. Puedes llevar a un Fake a que se comporte de maneras diferentes al darle diferentes datos.

Algo así como un simulador.

Si, los Fakes son simuladores.

Fakes no son Stubs, ¿o si?

No, fakes tienen comportamiento de negocio real; Los stubs no. Es más, ninguno de los otros Test Doubles de los que hemos hablado tiene un comportamiento de negocio real.

Entonces, los Fakes son diferentes a un nivel fundamental.

Claro que si. Podemos decir que un Mock es un tipo de Spy, un Spy es un tipo de Stub, y un Stub es un tipo de Dummy. Pero un Fake no es un tipo de ninguno. Es un tipo completamente diferente de Test Double.

Me imagino que los Fakes se pueden poner muy complicados.

Se pueden poner extremadamente complicados. Tan complicados que necesitan pruebas unitarias por sí mismos. Llevado a extremos, los Fakes se convierten en el sistema real.

Hmmm.

Si, “Hmmm”. Yo no escribo Fakes frecuentemente. La verdad, no he escrito uno en más de treinta años.

Wow! Entonces, ¿Qué sí escribes?. ¿Usas todos los otros Test Doubles?

En su mayoría uso Stubs y Spies. Y escribo los míos propio. No uso herramientas de bosquejos.

¿Usas los Dummies?

Sí, pero rara vez.

Y ¿que me dices de los Mocks?

Solamente cuando uso herramientas de bosquejos.

Pero acabas de decir que no usas herramientas de bosquejos.

Exactamente, normalmente no lo hago.

¿Por qué no?

Porque los Stubs y Spies son muy fáciles de escribir. Mi IDE lo hace trivial. Solo me posiciono sobre la interfaz y le digo al IDE que la implemente. Voila!, me crea un Dummy. Luego sólo hago modificaciones simples y lo convierto en un Stub o Spy. Así que rara vez necesito las herramientas de bosquejo.

Así que, ¿es solamente cuestión de conveniencia?

Si, y el hecho de que no me gusta la extraña sintaxis de las herramientas de bosquejos, y las complicaciones que añaden a mi configuración. Me parece que escribir mis propios Test Doubles es más simple en la mayoría de los casos..

OK, bueno, gracias por la conversación.

Cuando quieras.

Conclusiones

test-doubles

Se pudo ver claramente cómo en la “entrevista” se van definiendo qué son los dobles de pruebas,  sus características, y las diferencias entre ellos. También se mencionó acerca de los mocking frameworks.

Particularmente, concuerdo con Robert acerca de los mocks (el objeto mock, no el término general para Test Double). Martin Fowler también tiene la misma posición al respecto, y es que los mocks acoplan las pruebas a la implementación del componente, haciendo que cualquier cambio en la implementación (alguna mejora, cambio de algoritmo, etc) muy probablemente conlleve a modificar las pruebas. Este es un diseño muy poco mantenible cuando se tienen una gran cantidad de casos de pruebas, así que hay que tener mucho cuidado.

Si te gustó, ¡comparte!Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInShare on Reddit