Luz, cámara, TESTS!

Vero Isoardi
Unagi
Published in
5 min readOct 4, 2021

¿Qué tienen en común Tom Hanks, Meryl Streep, Angelina Jolie, Nicole Kidman y Brad Pitt? Todxs, alguna vez, usaron dobles para sus actuaciones.

Ahora, ¿qué tiene eso que ver con la programación? Muchísimo y en este artículo te lo cuento.

Ilustración en la que un sujeto se ve a si mismo reflejado en una pantalla

Cuando pensamos en desarrollar software casi por obligación deberíamos pensar en tests automáticos. Y escribir buenos tests, fáciles de leer y mantener es una parte fundamental de cualquier desarrollo.

Así es que nos encontramos con un montón de recursos para mejorar nuestros tests, entre los que se destacan los… wait for it… test doubles.

[Explicar las ventajas del testing está fuera del alcance de este artículo, pero si te interesa saber más, te sugiero darte una vuelta por este artículo de Lucas Hourquebie y este artículo de Agustín Serena.]

Los test doubles son especialmente útiles al momento de hacer tests de unidad, ya que en ellos queremos verificar que lo que estamos probando es correcto sin depender de las demás partes de nuestra aplicación.

El término hace referencia a un recurso que va a estar haciéndose pasar por el objeto original, el cual ocupa una parte real de nuestro sistema con un fin específico. La diferencia es que estos doubles suelen ser mucho más simples, ya que, en la mayoría de los casos, solo nos interesa que tenga algunas de las características del objeto original.

¿Doble para escenas de riesgo o para desnudos?

Así como los actores tienen diferentes dobles según la escena, en nuestros sistemas también tenemos diferentes doubles según lo que necesitemos verificar.

Así, dentro del término test doubles, incluimos doubles, fakes, stubs, mocks y spies. Parece que estoy hablando en un idioma nuevo, ¿no?

Veamos un ejemplo con RSpec en Ruby aplicando cada uno de ellos para verificar que un mailer se está usando correctamente.

Supongamos que tenemos un modelo User y un modelo Account, una clase Signup, que al registrarse el usuario envía un email, y un Signup mailer, que realiza el envío del email.

User model
Account model
Signup class
SignupMailer class

Doubles

Un double se asemeja y sustituye a un objeto real de nuestra aplicación durante la ejecución de los tests. Además de definirlos a nuestro gusto, podemos agregarles validaciones para que cumplan ciertos requisitos. Ejemplo: para que una persona X pueda ser doble de Hugh Jackman, al menos físicamente debería parecerse. Lo mismo pasa con los tests, pero con la ventaja de que podemos modificar al Hugh original, por ejemplo, para que tenga pelo rojo.

En RSpec podemos nombrar al objeto y definir que responde ante el llamado de un método específico usando el método double.

Stubs

Un stub se usa para definir la respuesta que esperamos que dé un objeto ante el llamado a un método determinado. Podemos agregar con qué parámetros debería llamarse y las validaciones necesarias para que simule adecuadamente la tarea.

Siguiendo con el ejemplo, tenemos que hacer 3 cosas:

  1. Crear al usuario, la cuenta y el mailer. Son objetos que necesitamos para probar que el envío del mail funciona, pero no hacen al mismo, es decir, es el momento ideal para usar doubles 🙌🏻.
  2. Definir qué respuesta esperamos que retorne SignupMailer al llamar al método signup. ¡Sí! Es el momento de usar un stub 🥳.
  3. Definir que esperamos que se llame al método deliver del SignupMailer.

Todo esto quedaría así 👇🏻:

Signup spec — Parte 1

Por un lado, creamos los doubles de User y Account haciendo uso de stubs para obtener las instancias de las clases. Y por el otro, creamos el double del mailer definiendo cuál va a ser la respuesta al método indicado.

Teniendo todos los objetos necesarios pasamos a hacer los stubs del SignupMailer definiendo qué métodos esperamos que se llamen, con qué parámetros y qué esperamos que respondan.

Ahora bien, todavía no estamos probando nada️. Y para eso necesitamos de los mocks.

PD: en mi mundo los stubs cumplen el rol de espectadores porque no importa si usa un double o un objeto real la respuesta que espera es la misma.

Mocks

Un mock, al igual que un stub, define la respuesta que espera que dé un objeto ante el llamado a un método. La diferencia está en que los mocks esperan que el llamado al método ocurra y de la forma en la que fue definido. Si el llamado no ocurre o no como fue definido, u ocurre más veces de las esperadas, el test falla.
Sigamos completando el ejemplo:

Signup spec — Parte 2

Acá pasan dos cosas: primero creamos el Signup object y llamamos al método que va a disparar todo lo que antes stubbeamos; y segundo, creamos los mocks para verificar que lo que esperamos se cumple. Pero nos falta algo… el logger.

STOP 🚫
Antes de pasar al logger vamos a hacer un mini breakpoint en los spies
.

Por último, para seguir con las asociaciones, el mock podría ser el espectador súper mega fan que se sabe todas las teorías, no se pierde un detalle y, cuando la historia no cierra, es el primero en quejarse.

Spies

Un spy es un stub que además guarda información de cómo fue llamado. Son especialmente útiles para testear diferentes servicios como lo son el envío de mails o llamados a diferentes APIs, comprobando cuántos llamados se hicieron a los mismos ante una situación puntual.

Hay dos formas de definirlos:

  1. A mano, donde primero se crea un stub y luego del llamado al método, se chequea que se haya llamado con un mock. Lo que permite mantener las 4 etapas de un test en orden, que serían: configuración, ejecución, verificación y destrucción.
    Esto es lo que hicimos en los pasos anteriores 🤯.
  2. Usando el método spy. La solución quedaría así 👇🏻:
Signup spec — Parte 3

Fakes

Un fake es un PORO (Plain Old Ruby Object) que se usa en los objetos a testear. Constan de una implementación funcional, que busca cumplir con el objetivo para el que fueron creados.

✅ Pro: le das el comportamiento que quieras.
❌ Contra: hay que implementarlo.

Para completar el ejemplo nos faltan los loggers. Tenemos qué hacer dos cosas: crear el PORO y usarlo en el test.

FakeLogger class
Signup spec — Parte 4 y última

Y así es como haciendo uso de todos los tests doubles, probás que un mail se envía correctamente.

Lo ideal es usar stubs, mocks y spies antes que fakes PERO, siempre hay un pero 😒, si la implementación queda difícil de leer y bastante larga quizás es momento de ir por un fake.

Ahora bien, ¿por qué usar estos recursos?

  1. Aportan legibilidad a nuestros tests, simplificando la configuración previa y quedando a la vista la relación entre nuestro sujeto de prueba y los demás objetos.
  2. Permiten aislar el comportamiento a probar, testeando únicamente la funcionalidad que queremos probar.
  3. Ahorran tiempo en el uso de servicios como llamados a APIs o envíos de mails, evitando llamados innecesarios con respuestas fácilmente mockeables.
  4. Permiten controlar las respuestas a nuestro sujeto de prueba lo que evita que falle por causas externas al mismo.

Podés encontrar más información sobre métodos disponibles y usos en la documentación de RSpec Mocks.

¿Conocías esto? Me encantaría seguir aprendiendo sobre testing (no importa cuando ni donde leas esto), así que usá los comentarios para contarme 🤗. Y no olvides dejar 👏🏻 si te gustó el artículo.

--

--

Vero Isoardi
Unagi
Writer for

Desarrolladora Ruby on Rails en Unagi. Estudiante de sistemas y diseño multimedia. Súper curiosa. ¿Soy realmente yo si no estoy preguntando el porqué de todo?🤓