Profile Image

Carlos Vigueras

Senior Backend Developer

Testing – AutoFixture para creación de valores aleatorios e instancias.

En primer lugar de todos, quisiera matizar, que AutoFixture no es un Patrón de diseño, es tan solo una librería de código abierto, la cual podemos hacer uso cualquiera que la necesitemos descargándola de los Paquetes Nugets. En su proyecto de GitHub es definida de la siguiente forma:

AutoFixture es una librería de código abierto para .NET diseñada para minimizar la fase de ‘Arrange’ de sus pruebas unitarias con el fin de maximizar la mantenibilidad. Su objetivo principal es permitir a los desarrolladores centrarse en lo que se está probando en lugar de cómo configurar el escenario de prueba, facilitando la creación de gráficos de objetos que contienen datos de prueba.

Cuando tenemos que realizar Tests en los que están implicados objetos complejos, podemos utilizar AutoFixture, y de este modo nos olvidamos de ir asignando campo a campo un objeto, imaginad un objeto muy largo, o muy complejo, no sólo nos cuesta la creación manual del mismo, si no que como hemos visto ya, perdemos un la semántica de los Tests. Además la creación de este tipo de Tests se nos convertirá en una tarea compleja, escribiendo código que no es relevante para el Test.

Si lo que queremos es automatizar la creación de nuestros objetos o entidades con valores aleatorios estaremos usando la librería correcta, ya que lo conseguiremos de una forma rápida y limpia.

¿Pero con AutoFixture sólo puedo crear objetos con propiedades aleatorias?

Realmente, podremos crear objetos de forma automatizada con campos aleatorios, y si lo deseamos en algún caso también personalizados, pero eso lo veremos en breve.

Como siempre vamos a ver un ejemplo, y lo veremos con el proyecto del User que veíamos en las entradas de Object Mother y Patrón Builder:

User.cs

    public class User
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string PhoneNumber { get; set; }
        public string Gender { get; set; }
        public string Email { get; set; }
        public string MobileNumber { get; set; }
        public int Age { get; set; }
        

        public User(string name,
                    string lastName,
                    string city,
                    string phoneNumber,
                    string gender,
                    string email,
                    string mobileNumber,
                    int age)
        {
            Name = name;
            LastName = lastName;
            City = city;
            PhoneNumber = phoneNumber;
            Gender = gender;
            Email = email;
            MobileNumber = mobileNumber;
            Age = 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 User("Pepito",
                                              "Grillo",
                                              "Murcia",
                                              "000000000",
                                              "Hombre",
                                              "pepitogrillo@anyemail.com",
                                              "000000000",
                                              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 = new User("Pepito",
                                              "Grillo",
                                              "Murcia",
                                              "000000000",
                                              "Hombre",
                                              "pepitogrillo@anyemail.com",
                                              "000000000",
                                              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 = new User("Pepito",
                                              "Grillo",
                                              "Murcia",
                                              "000000000",
                                              "Hombre",
                                              "pepitogrillo@anyemail.com",
                                              "000000000",
                                              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 = new User("Pepito",
                                              "Grillo",
                                              "Murcia",
                                              "000000000",
                                              "Hombre",
                                              "pepitogrillo@anyemail.com",
                                              "000000000",
                                              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)

Ahora la clase User.cs contiene ocho propiedades, que si bien no son muchas, si son más de las que antes teníamos, y poco a poco se va haciendo un poco mas complicado rellenar el objeto, aparte de que tenemos que ir repitiendo por cada uno de los Tests la creación del objeto.

Si, muchos me podéis decir que os crearíais un método que generase un objeto User.cs con el parámetro Age pasado por valor, ya que es el único cambiante, pero esto, una vez mas rompería la semántica de los Tests, y aparte la creación del objeto, aunque sea sólo una vez no nos la quita nadie.

Como ya os habréis dado cuenta, aquí la única propiedad que difiere de las demás es Edad, ya que el propósito de los Tests es chequear la edad que tiene el User y en base a eso, envía un mensaje u otro, por lo que podríamos crear un objeto User.cs con valores aleatorios, excepto con la edad que podría ser cambiante, ¿verdad?

Pues vamos a ello, lo primero de todo, hemos de instalar AutoFixture en nuestro proyecto de Tests:

Para este caso instalamos la Versión 4.18.1 que es la última que hay actualmente. Una vez instalado AutoFixture, vamos a proceder a usarlo en nuestros Tests. Vamos a refactorizarlos:

UserShould.cs con AutoFixture

public class UserShould
{
    private Home home;
    private Fixture fixture;

    [SetUp]
    public void Setup()
    {
        home = new Home();
        fixture = new Fixture();
    }

    [Test]
    public void get_message_under_eighteen_years()
    {
        //Given
        var givenUserUnderEighteen = fixture.Build<User>()
                                            .With(u => u.Age, 16)
                                            .Create();

        //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 = fixture.Build<User>()
                                            .With(u => u.Age, 29)
                                            .Create();

        //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 = fixture.Build<User>()
                                            .With(u => u.Age, 46)
                                            .Create();

        //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 = fixture.Build<User>()
                                            .With(u => u.Age, 67)
                                            .Create();

        //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: JavaScript (javascript)

Ahora en los Tests de UserShould.cs usamos AutoFixture, y le hemos dado mas semántica a nuestros Tests,

¿Por qué?

Muy sencillo, ahora podemos ver como pedimos un usuario con una edad concreta en cada uno de los Tests, y nos queda claro cual es la regla de negocio que se ha de cumplir para que el Tests sea correcto, de la forma anterior, veíamos un montón de propiedades, y aunque en el ejemplo era bastante obvio a que se hacía referencia, no en todos los casos va a ser así.

Vamos a ver como nos ha creado AutoFixture el objeto:

Podemos ver, cómo a las propiedades de User.cs les ha dado un valor aleatorio, excepto a la edad, que es justo lo que le hemos indicado.

¿Qué mas da el nombre o el apellido del usuario o las demás variables?

Para estos Tests solo nos importa la edad, lo demás queda asignado de forma automática. Y como hemos dicho los Tests ahora tienen mas semántica y se entiende mejor el propósito y la regla de negocio que ha de cumplirse.

Pero como todo no va a ser fantástico y maravilloso, os pregunto:

¿Qué sucede si usamos AutoFixture con objetos inmutables? ¿Creéis que funcionará por defecto?

!Os leo en los comentarios!

One thought on “Testing – AutoFixture para creación de valores aleatorios e instancias.

Deja una respuesta

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