Testing Duck Types
09-11-2023
Al trabajar con la programación orientada a objetos, especialmente en sistemas complejos, a menudo surge la necesidad de trabajar con tipos abstractos en lugar de clases concretas. Este enfoque, conocido como "duck typing", nos permite enfocarnos en el comportamiento que un objeto puede soportar, en lugar de la clase del objeto per se. En Python, "duck typing" es un concepto clave debido a su naturaleza dinámica y su filosofía de "si camina como un pato y hace quack como un pato, entonces debe ser un pato".
Definiendo la Interfaz de Preparación de Eventos
Imaginemos que estamos diseñando un sistema que organiza eventos. Tenemos
diferentes tipos de organizadores de eventos, cada uno con su propio conjunto de
responsabilidades. Para mantener un diseño limpio, definimos una interfaz
abstracta EventOrganizer
y aseguramos que todos los organizadores la
implementen.
from abc import ABC, abstractmethod class EventOrganizer(ABC): @abstractmethod def organize(self, event): pass
Cada organizador de eventos (Caterer
, Musician
, Decorator
) implementa el
método organize
para preparar su parte del evento.
class Caterer(EventOrganizer): def organize(self, event): # Implementación específica del Caterer pass class Musician(EventOrganizer): def organize(self, event): # Implementación específica del Musician pass class Decorator(EventOrganizer): def organize(self, event): # Implementación específica del Decorator pass
La clase Event
es responsable de orquestar la preparación, invocando el método
organize
de cada organizador.
class Event: def __init__(self, organizers): self.organizers = organizers def prepare(self): for organizer in self.organizers: organizer.organize(self)
Escribiendo Pruebas Compartibles para Duck Types
El uso de "duck types" nos lleva a la pregunta de cómo probar efectivamente este
comportamiento. La clave es escribir pruebas que puedan ser compartidas por
todos los jugadores del rol EventOrganizer
. Usaremos el framework unittest
de Python para hacer esto.
Creamos un test case base que cualquier organizador de eventos puede utilizar
para probar si cumple con la interfaz EventOrganizer
.
import unittest class EventOrganizerTestCase(unittest.TestCase): def setUp(self): self.object = self.get_organizer() @abstractmethod def get_organizer(self): pass def test_implements_event_organizer_interface(self): self.assertTrue(hasattr(self.object, 'organize'))
A continuación, extendemos este test case para cada tipo de organizador de eventos.
class CatererTest(EventOrganizerTestCase): def get_organizer(self): return Caterer() class MusicianTest(EventOrganizerTestCase): def get_organizer(self): return Musician() class DecoratorTest(EventOrganizerTestCase): def get_organizer(self): return Decorator()
Con estos test cases, podemos asegurarnos de que cada organizador implemente el
método organize
requerido por la interfaz EventOrganizer
.
Pruebas de Colaboración
Además de probar que los organizadores cumplen con la interfaz, necesitamos
asegurarnos de que la clase Event
interactúa correctamente con los
EventOrganizer
. Esto se logra simulando (mocking) los objetos y asegurándonos
de que se llama al método organize
.
class EventTest(unittest.TestCase): def test_prepares_with_organizers(self): organizer_mock = unittest.mock.Mock(spec=EventOrganizer) event = Event([organizer_mock]) event.prepare() organizer_mock.organize.assert_called_once_with(event)
Ejecutar estas pruebas asegura que nuestro sistema está bien integrado y que las interacciones entre los diferentes componentes son como se esperan.
Conclusión
En conclusión, "duck typing" y las pruebas compartibles proporcionan un marco poderoso para construir y asegurar sistemas flexibles y mantenibles. Aunque Python no tiene interfaces en el mismo sentido que otros lenguajes de programación, la filosofía de "duck typing" y las pruebas sólidas nos permiten diseñar e implementar sistemas robustos y flexibles.