Profile Image

Carlos Vigueras

Senior Backend Developer

Testing – Object Mother

En esta entrada voy a hablar sobre un tema que me parece muy interesante, y aunque no está directamente relacionado con TDD, si lo está con Testing de aplicaciones, y en este caso, como siempre lo voy a orientar a .NET.

El patrón Object Mother, lo presentó Martin Fowler en el año 2006, y los define de una forma clara y sencilla de esta forma:

Un objeto madre u Object Mother, es un tipo de clase que se utiliza en las pruebas para ayudar a crear objetos de ejemplo que se utilizan para las pruebas.

Desde mi experiencia y punto de vista, yo prefiero usar siempre Object Mother en mi Tests antes que el Patrón Builder, el cual he visto muchas veces a la hora de construir objetos de ejemplo para testear un caso de uso, o cualquier otro tipo de Test.

El Patrón Builder en los test personalmente no me gusta, ya que estamos añadiendo una lógica innecesaria que creo que deberíamos evitar, aunque en contraposición hay muchos expertos que se expresan a favor de usarlos, y como todo en la vida, para gustos los colores.

Una cosa importante a tener en cuenta, es que nuestros Tests nunca deberían aplicar lógica alguna en su implementación, o en todo caso la mínima posible. Porque si aplicamos lógica, ¿quien testea el test?

Aunque con el Patrón Builder, no es que estemos añadiendo una lógica en sí, si que estamos creando una clase específica para construir un objeto determinado y estamos introduciendo complejidad a nuestros Tests.

Con Object Mother, lo que conseguimos es centralizar una serie de objetos con una característica especial y hacer uso de ellos desde diferentes Suites de Tests que implementemos, de esta forma, a la hora de ir creando objetos, no hemos de ir dejando trozos de código repetidos por todos nuestros Tests.

Imaginemos que vamos a comenzar a desarrollar una aplicación que es solo de uso para personas mayores de 18 años, y que dependiendo la edad que tengas, vas a tener acceso a unos descuentos u otro. A la hora de acceder a la aplicación, hemos de realizar las siguientes comprobaciones:

  • Dado un usuario menor de 18 años, cuando intenta acceder a la aplicación, quiero que se le muestre un mensaje: “Aplicación web solo para personas mayores de edad“.

  • Dado un usuario mayor de 18 años y menor de 30 acceda a la aplicación, quiero que le muestre un mensaje: “Consigue un descuento de 20€ en tu primer pedido“.

  • Dado un usuario mayor de 30 años y menor de 65 acceda a la aplicación, quiero que le muestre un mensaje: “Consigue un descuento de 25€ en tu primer pedido“.

  • Dado un usuario mayor de 65 años acceda a la aplicación, quiero que le muestre un mensaje: “Consigue un descuento de 30€ en tu primer pedido“.

A lo largo de todos nuestros Tests, vamos a necesitar juegos de datos con estos 4 tipos de usuarios, no solo para cuando acceda a la aplicación mostrar el mensaje, si no, posteriormente cuando compre un producto saber que tipo de descuento se le va a aplicar, y para otros muchos casos mas.

Si en todos estos Tests, nos vamos creando para cada uno de ellos una instancia, nos vamos a encontrar con mucho código repetido por todos lados, pero si usamos Object Mother, estos datos los vamos a tener centralizados. Pero sin meter mas rollo, vamos a ver un ejemplo con .NET y NUnit, y esta vez, aparte de exponeros el código, os referenciaré el GitHub asociado hecho con TDD:

Clase User.cs

    public class User
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public User(string name, int age)
        {
            Name = name;
            Age = age;
        }
        public static User Create(string name, int age)
        {
            return new User(name, age);
        }
    }Lenguaje del código: JavaScript (javascript)

Clase Home.cs

    public class Home
    {
        public string Access(User user)
        {
            switch (user.Age)
            {
                case < 18:
                    return "Aplicación web solo para personas mayores de edad";
                case < 30:
                    return "Consigue un descuento de 20€ en tu primer pedido";
                case < 65:
                    return "Consigue un descuento de 25€ en tu primer pedido";
                case > 65:
                    return "Consigue un descuento de 30€ en tu primer pedido";
                default:
                    return string.Empty;
            }
        }
    }Lenguaje del código: PHP (php)

Suite de Tests UserShould.cs

public class UserShould
{
    private Home home;
    [SetUp]
    public void Setup()
    {
        home = new Home();
    }

    [Test]
    public void get_message_under_eighteen_years()
    {
        //Given
        var givenUserUnderEighteen = User.Create("Pepito Grillo", 16);

        //When
        var message = home.Access(givenUserUnderEighteen);

        //Then
        Assert.IsTrue(string.Equals(message, "Aplicación web solo para personas mayores de edad", 
            StringComparison.OrdinalIgnoreCase));
    }

    [Test]
    public void get_message_over_eighteen_under_thirty_years()
    {
        //Given
        var givenUserOverEighteenUnderThirty = User.Create("Pepito Grillo", 29);

        //When
        var message = home.Access(givenUserOverEighteenUnderThirty);

        //Then
        Assert.IsTrue(string.Equals(message, "Consigue un descuento de 20€ en tu primer pedido",
            StringComparison.OrdinalIgnoreCase));
    }


    [Test]
    public void get_message_over_thirty_under_sixty_five_years()
    {
        //Given
        var givenUserOverThirtyUnderSixtyFive = User.Create("Pepito Grillo", 46);

        //When
        var message = home.Access(givenUserOverThirtyUnderSixtyFive);

        //Then
        Assert.IsTrue(string.Equals(message, "Consigue un descuento de 25€ en tu primer pedido",
            StringComparison.OrdinalIgnoreCase));
    }

    [Test]
    public void get_message_over_sixty_five_years()
    {
        //Given
        var givenUserOverSixtyFive = User.Create("Pepito Grillo", 67);

        //When
        var message = home.Access(givenUserOverSixtyFive);

        //Then
        Assert.IsTrue(string.Equals(message, "Consigue un descuento de 30€ en tu primer pedido",
            StringComparison.OrdinalIgnoreCase));
    }
}Lenguaje del código: PHP (php)

Si os fijáis en la Suite de Tests de UserShould.cs, para cada uno de los test he ido llamando al método Create de User, y he ido creando un usuario con diferentes edades que necesito para realizar el testeo, y sí, podría llevámerlo a un método en la misma Suite, donde se le pasase un nombre y una edad y me retornase el objeto, pero de esta forma, los tests no quedan claros, no se ve a simple vista que es lo que se quiere realmente testear (tal vez en este ejemplo si, en otros mas complejos, creedme que no).

Los Tests han de servirnos de documentación, por eso es muy importante tener unos Tests claros y concisos, que se expresen por si solos y nada mas verlos, sepamos cual es su propósito.

Si en vez de ir creando un objeto en cada uno de los Tests, nos creamos un Objeto Madre u Object Mother centralizado, vamos a pedirle y recibir exactamente lo que necesitamos, eso sí, siempre usando un nombre de método declarativo y que exprese que es lo que devuelve. Veamos como quedaría la Suite de Tests y el Objeto Madre:

UserShould.cs

    public class UserShould
    {
        private Home home;
        [SetUp]
        public void Setup()
        {
            home = new Home();
        }

        [Test]
        public void get_message_under_eighteen_years()
        {
            //Given
            var givenUserUnderEighteen = UserMother.UserUnderEighteen();

            //When
            var message = home.Access(givenUserUnderEighteen);

            //Then
            Assert.IsTrue(string.Equals(message, "Aplicación web solo para personas mayores de edad",
                StringComparison.OrdinalIgnoreCase));
        }

        [Test]
        public void get_message_over_eighteen_under_thirty_years()
        {
            //Given
            var givenUserOverEighteenUnderThirty = UserMother.UserOverEighteenUnderThirty();

            //When
            var message = home.Access(givenUserOverEighteenUnderThirty);

            //Then
            Assert.IsTrue(string.Equals(message, "Consigue un descuento de 20€ en tu primer pedido",
                StringComparison.OrdinalIgnoreCase));
        }


        [Test]
        public void get_message_over_thirty_under_sixty_five_years()
        {
            //Given
            var givenUserOverThirtyUnderSixtyFive = UserMother.UserOverThirtyUnderSixtyFive();

            //When
            var message = home.Access(givenUserOverThirtyUnderSixtyFive);

            //Then
            Assert.IsTrue(string.Equals(message, "Consigue un descuento de 25€ en tu primer pedido",
                StringComparison.OrdinalIgnoreCase));
        }

        [Test]
        public void get_message_over_sixty_five_years()
        {
            //Given
            var givenUserOverSixtyFive = UserMother.UserOverSixtyFive();

            //When
            var message = home.Access(givenUserOverSixtyFive);

            //Then
            Assert.IsTrue(string.Equals(message, "Consigue un descuento de 30€ en tu primer pedido",
                StringComparison.OrdinalIgnoreCase));
        }
    }Lenguaje del código: PHP (php)

UserMother.cs

    public class UserMother
    {
        public static User UserUnderEighteen()
        {
            return User.Create("Pepito Grillo", 16);
        }

        public static User UserOverEighteenUnderThirty()
        {
            return User.Create("Pepito Grillo", 29);
        }

        public static User UserOverThirtyUnderSixtyFive()
        {
            return User.Create("Pepito Grillo", 46);
        }

        public static User UserOverSixtyFive()
        {
            return User.Create("Pepito Grillo", 67);
        }
    }Lenguaje del código: PHP (php)

Como podéis ver ahora tenemos un Objeto Madre UserMother, con métodos que devuelven diferentes tipos de usuarios, de diferentes edades según necesitemos y con unos nombres de métodos que expresan exactamente que es lo que devuelven, por lo que en los Tests hacemos alusión a esos métodos dependiendo lo que necesitemos, si es un usuario menor de 18, mayor de 18 y menor que 30… etc.

De esta forma hemos centralizado la creación de los datos, y no solo eso, si no que en los Tests, se ve de una forma mas sencilla que es lo que pretende dicho Test, además, tenemos el código mas ordenado, mas limpio, mas entendible, centralizado y legible.

Os dejo el repositorio de Github que he creado con el ejemplo completo.

¿Vosotros preferís Object Mother o Patrón Builder? Os leo en comentarios!

3 thoughts on “Testing – Object Mother

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *