Profile Image

Carlos Vigueras

Senior Backend Developer

Testing – Patrón Builder

Hace unos días escribí una entrada sobre el Patrón Object Mother de Martín Fowler, en la que hacía referencia al Patrón Builder, del cual, la verdad, es que no soy muy partidario de utilizarlo en Test, pero aún así y a petición de un lector que puso un comentario solicitando un ejemplo voy a hablar de él.

El Patrón Builder es un patrón de diseño creacional, el cual nos permite crear objetos complejos, y no necesariamente se usa para crear estructuras de datos en Test, si no que se usa también en muchas ocasiones en código productivo.

No obstante hay muchos programadores que lo usan para crear objetos complejos y usarlos en Suite de Tests, sobre este tema os encontraréis muchos detractores de usar este patrón en Test, y otros tantos a favor, por lo que esto va en cuestión de gustos.

En esta entrada solo me voy a dedicar a explicar como usarlo como una alternativa a los Object Mother, bueno pues como se suele decir… vamos al grano!!

Cuando estamos testeando en alguna aplicación alguna funcionalidad, siempre hemos de procurar discernir la parte de los datos de la parte de Test, y de este modo podremos tener unos test mas claros y entendibles, ¿cuantas veces habré dicho esas palabras?, lo que es igual a decir que mejoraremos la semántica de los Tests, ya que estaremos separando la parte de creación de datos con la parte del testeo en sí.

Sin irnos por los Cerros de Úbeda, vamos a entrar en materia, y para ello pues vamos a usar el mismo ejemplo que tenemos de Object Mother, modificándolo y lo dejaré en mi GitHub para que también le echéis un vistazo.

Para que el ejemplo sea mas entendible no voy a usar la creación de un objeto complejo, pero entended que el Patrón Builder es para la creación de objetos complejos.

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)

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)

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 = new UserBuilder().WithName("Pepito")
             .WithAge(16)
             .Build();

         //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 = new UserBuilder().WithName("Pepito")
             .WithAge(29)
             .Build();

         //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 = new UserBuilder().WithName("Pepito")
             .WithAge(46)
             .Build();

         //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 = new UserBuilder().WithName("Pepito")
             .WithAge(67)
             .Build();

         //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)

UserBuilder.cs

public class UserBuilder
{
    private string Name;
    private int Age;

    public UserBuilder WithName(String name)
    {
        this.Name = name;
        return this;
    }

    public UserBuilder WithAge(int age)
    {
        this.Age = age;
        return this;
    }

    public User Build()
    {
        return new User (Name, Age);
    }
}Lenguaje del código: PHP (php)

En este caso con el Patrón Builder nos estamos apoyando en una clase externa como con el Patrón Object Mother para conseguir los datos tal y como los queremos para nuestros tests, pero en mi opinión, no queda tan claro que es exactamente lo que queremos conseguir con esos tests, es decir, el nombre de las funciones no es tan declarativo como en Object Mother, y esto, bajo mi punto de vista, no nos ofrece una buena semántica a nuestros Tests.

Sin lugar a dudas, es una forma diferente de crear datos para Tests, y para gustos, como dicen, los colores.

A día de hoy para separar creación de datos y test he usado estas cuatro técnicas:

  • AutoFixture con entidades inmutables

  • Patrón Object Mother combinado con AutoFixture, del que también hablaré.

Estoy seguro que muchos de vosotros conocéis alguna mas, por lo que me gustaría me las expongais en comentarios.

¿Cuales habéis usado vosotros?

¿Que opináis del Patrón Builder en los Tests?

4 thoughts on “Testing – Patrón Builder

  1. Enhorabuena una vez más por el contenido Carlos!

    Hasta hace muy poco desconocía estos patrones para crear clases de test. Y todavía ando dubitativo sobre que alternativa usar.

    Por presentar otro punto de vista, comparto por aquí la opinión de Nat Pryce. El cual presenta el patrón Builder como la mejor opción por una cuestión de mantenibilidad.

    Dada la necesidad de obtener diferentes variaciones de un mismo objeto, en el patrón Object Mother necesitaríamos tantos métodos estáticos como variaciones necesitemos. Mientras que con el Builder, la clase builder no tendría porque crecer innecesariamente.

    http://www.natpryce.com/articles/000714.html
    http://www.natpryce.com/articles/000724.html
    https://blog.ploeh.dk/2017/08/15/test-data-builders-in-c/

    Sinceramente, por mi corta experiencia con estos patrones, me apoyo en lo meramente teórico. No puedo decir que me haya funcionado mejor un patrón que el otro.

    Sin embargo, por poner otro ejemplo, en mi equipo son detractores del patrón Builder precisamente por lo contrario que dice la teoría. En su experiencia las clases builder se terminan volviendo más difíciles de mantener con el paso del tiempo. Si bien es cierto, que analizándolo en profundidad esto suele ser un smell de que la clase que estamos testeando tiene más responsabilidades de las que debería y está pidiendo refactoring.

    Un abrazo compañero!

    1. Buenas tardes Delineante, en primer lugar, como siempre gracias por leerme, como sabes me anima mucho a seguir escribiendo. Con respecto a lo que comentas tienes razón, aunque como todo en la vida, ambos tienen sus pros y sus contras y hay que ver cuando es beneficioso usar uno u otro, cada cual de ellos es una forma diferente de orientar la creación de datos en Tests y ambas podrían ser buenas opciones.
      Lo que sí creo que siempre hay que tener en cuenta es en no pensar que pasará en un futuro… ¿y si mi aplicación crece y crece? Pues cuando crezca ya veremos… por el momento implementa siempre la opción que sea la más sencilla adaptada al diseño de Tests que tengas. Para refactorizar siempre tendrás tiempo. Cuando estamos diseñando una aplicación nunca hay que pensar en que podrá pasar en un futuro, hay que implementar la solución más sencilla, eso sí, siempre desarrollando una App escalable y con unas buenas Suites de Tests. Un saludo!

Deja una respuesta

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