Memorias Impuras #10

Bello es mejor que feo
Explícito es mejor que implícito
Simple es mejor que complejo
   - Del Zen de Python

 Buscar el camino es el camino

Testing como forma de aprendizaje capitulo mil.

El éxito de la revisión de código o code review está más a tado a las personas que a la metodología.

Durante el último tiempo, mis compañeros y compañeras, al revisar mi código me llevaron por una senda de aprendizaje continuo. En esta oportunidad: Fixtures Parametrizados

/images/cositos-chihiro.gif

El código estaba “casi” listo, y lo iba a mandar a un último review. Lo que hice fue un método que genera thumbnails a partir de una imágen dada usando la librería Pillow. El problema que tenía era que luego de generar el thumb, las imágenes quedaban rotadas a la izquierda. La solución: extraer los datos exif de la imagen original y guardarlos en la nueva imagen. En estos metadatos viene, en general, la orientación con que fue sacada la foto. Algo así:

def _create_thumb(self, profile_image: bytes) -> bytes:
with Image.open(io.BytesIO(profile_image)) as image:
    format = image.format
    exif_data = image.getexif()
    image.thumbnail(self.THUMB_SIZE)

    encoding_parameter = io.BytesIO()
    image.save(encoding_parameter, format, exif=exif_data)
    thumb_image_in_bytes = encoding_parameter.getvalue()

return thumb_image_in_bytes

Cuando me fuí a dormir me asaltó la duda ¿Qué pasaría si la imagen no tiene metadatos, exif_data sería nulo, puedo guardar un valor nulo? Desde la cama me respondí a mi mismo:

if not exif_data:
    image.save(encoding_parameter, format)
else:
    image.save(encoding_parameter, format, exif=exif_data)

Ya le había hecho una batería de test al PR que review mediante, habían quedado muy completos. Así que codee mi solución y push. Al rato mi reviwer me pregunta

  • Can we unit test exif data is saved?
  • Of course! respondí.

Bueno, no fue tan fácil.

  1. Mis tests usaban un fixture que generaba una imagen de 500 x 500 vacía, sin metadata
  2. Se me currió parametrizar el fixture, incorporar otra imagen con metadata y una verificación entre las dos imagenes, la que le pasaba y la creada.

Para parametrizar el test me fuí a la doc de Pytest, ahí existe algo llamado: Parametrización indirecta

Using the indirect=True parameter when parametrizing a test allows to parametrize a test with a fixture receiving the values before passing them to a test:

Osea que con ese parámetro en el decorador de parametrización, el test recibe los valores del fixture pero antes se le pasan los valores definidos y luego se incorporan al test.

Este es un ejemplo en el que paso parametrizo 2 variables, una de ellas el fixture:

import pytest


@pytest.fixture
def fixt(request):
    if not request.param:
        return f"No hay metadata"
    else:
        return f"Hay metadata"


@pytest.mark.parametrize(
    "fixt, expected",
    [(True, f"Hay metadata"), (False, f"No hay metadata")],
    indirect=["fixt"],
)
def test_indirect(fixt, expected):
    assert fixt == expected

Ahora bien, mientras escribo esto me doy cuenta que al extraer los datos exif se va llenando un diccionario, y que cuando no hay datos, el diccionario está vacío. No vuele un dato de tipo Null. Esto lo comprobé debuggeando cundo escribía el test.

¡Entonces no necesito esos condicionales para guardar la imagen! Puedo refactorear, evaluar si pasan los test y escribir un código un poco más lindo. y todo gracias a un buen review y los test. Primero escribí un código bien explícito, al agregar mas test que cubran todas las condiciones, me doy cuenta que puedo hacerlo más bello.

Bello es mejor que feo
Explícito es mejor que implícito
Simple es mejor que complejo
   - Del Zen de Python

 Buscar el camino es el camino

Imagen: giphy

comments powered by Disqus