No hagas estos tests

por Fran Iglesias

O al menos, no de esta manera.

Objetos mensaje

Eventos, Comandos y Queries, son objetos que nos interesan por su significado y que llevan datos para sus respectivos handlers. En ocasiones me han planteado: ¿no hay que testear esto?

Quiero decir:

class PostWasCreated
{
    private string $title;
    private string $body;
    
    public function __construct(string $title, string $body)
    {
        $this->title = $title;
        $this->body = $body;
    }
    
    public function title(): string
    {
        return $this->title;
    }
    
    public function body(): string
    {
        return $this->body;
    }
}

– ¿No hay que testear que se construye bien?

class PostWasCreatedTest extends TestCase
{
    public function testShouldCreateEvent(): void
    {
        $event = new PostWasCreated('Title', 'Body');
        
        self::assertTrue('Title', $event->title());
        self::assertTrue('Body', $event->body());
    }
}

– Pues no. Tanto si es en una situación de TDD, como si en QA, este test es una pérdida de tiempo.

– Pero imagínate que te lías y asignas el valor de body a title y viceversa.

– Es que no lo tienes que testear así.

En primer lugar, buscamos testear comportamientos. Asignar los parámetros pasados a las propiedades no es un comportamiento en sí mismo.

– ¿Pero no testeas que se crean objetos consistentes? Si hasta tienes una kata basada en eso en este mismo blog.

– Por supuesto, pero lo que verificamos es que construimos objetos que cumplen reglas de negocio y mantienen invariantes. De nuevo: la inicialización de propiedades no es un comportamiento.

– ¿Entonces?

– La forma adecuada de testear esto es testeando su handler.

Imaginemos algún servicio de reservas que notifica a la usuaria por correo electrónico que la reserva ha sido realizada correctamente. Esto se hace porque al realizar la reserva, se lanza un evento BookingWasCreated, que contiene los datos básicos de la misma.

Uno de los suscriptores o listeners de este evento es SendConfirmationMessage que compone un mensaje y lo envía a través de un Mailer, aquí representado con un doble.

El razonamiento es el siguiente: si el evento está bien construido, el mensaje se construirá como se espera. Si el test falla porque el mensaje resultante es, por ejemplo, “Booked from 15/5/2021 to 15:35 @ 16:20”, está claro que en alguna parte se han cruzado los datos.

class SendConfirmationMessageTest extends TestCase
{
    public function testShouldNotify(): void
    {
        $event = new BookingWasCreated(
            'fran@example.com', 
            '15/5/2021', 
            '15:35', 
            '16:20'
        );
        
        $message = new Message('Booked from 15:35 to 16:20 @ 15/5/2021');
        $message->to('fran@example.com');
        
        $mailer = $this->createMock(Mailer::class);
        $mailer
            ->expect(self::once())
            ->method('send')
            ->with($message);
            
        $sendConfirmationMessage = new SendConfirmationMessage($mailer);
        
        $sendConfirmationMessage->handle($event);  
    }
}

Todo esto aplica igualmente a DTO y otros objetos parámetros que solo llevan datos y no tienen comportamientos.

April 8, 2021

Etiquetas: testing   tdd  

Temas

good-practices php testing blogtober19 refactoring design-principles tdd misc legacy python design-patterns bdd tools tips ddd bbdd soft-skills ruby javascript books api sql ethics swift