Publicado originalmente en carlosvigueras.es
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);
}
}
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;
}
}
}
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));
}
}
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);
}
}
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?
Top comments (0)