TDD con el patrón Test Data Builder

Desde hace algunos meses que vengo metido en todo esto del TDD y recientemente en el BDD, justamente la anterior semana que recien encontré un par de videos, de la invaluable comunidad ALT.NET Hispano, recomendables para BDD, uno de mi amigo Hernan García (Behaviour Driven Development) y otro el de Jorge Gamba (Haciendo BDD con MSpec), mientras veia este ultimo se menciona de pasada un patron llamado Test Data Builder, me entro la curiosidad y me puse a revisarlo, menuda sorpresa me lleve al descubrir que era justamente algo que llevaba haciendo de manera intuitiva hace ya unas semanas para organizar los constructores dentro de mis pruebas unitarias.

La documentacion de Test Data Builder menciona que es una alternativa a otro Patron usado frecuentemente, el Object Mother, que obviamente todos lo hemos usado aunque quiza no sabiamos su nombre formal, pero bueno basta de bla bla y mostremos código.

Ocurre que cuando estamos haciendo pruebas de nuestro codigo, en muchos casos nos encontramos ante la necesidad de crear objetos con diferentes estados (llamese estados a las multiples combinaciones de valores que pueden tener sus atributos), supongamos que dispongo de una clase ridiculamente simple llamada Person, como se muestra a continuacion:

public class Person
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string Address { get; set; }

        public Person(string Name, string Address, DateTime DateOfBirth)
        {
            // TODO: Complete member initialization
            this.Name = Name;
            this.Address = Address;
            this.DateOfBirth = DateOfBirth;
        }
    }

Esta clase puede ir creciendo en sus propiedades por lo que el constructor dejara de ser util pronto, pero lo dejo ahi para “dramatizar” la complejidad con la que nos enfrentaremos.

Aqui un par de pruebas tambien ridiculas pero que tienen por objetivo mostrar los patrones mencionados:


[TestMethod]
public void TestPersonInvalidAge()
{
    Person enrique = PersonObjectMother.CreatePersonWithInvalidDate(new DateTime(1725, 1, 1));
    var age = (DateTime.Now.Year - enrique.DateOfBirth.Year);
    age = enrique.DateOfBirth > DateTime.Now.AddYears(-age) ? age - 1 : age;
    Assert.IsTrue(age > 1 && age <= 130, "Age must be between 1 and 130, currently is " + age);
}

[TestMethod]
public void TestPersonInvalidName()
{
    Person enrique = PersonObjectMother.CreatePersonWithInvalidName("123");
    Assert.IsTrue(Regex.IsMatch(enrique.Name, "^[a-zA-Z'.]{1,40}$"), "The name should only contain letters");
}

Aqui la implementacion del Object Mother:

public static class PersonObjectMother
    {
        public static Person CreatePersonWithInvalidDate(DateTime dateTime)
        {
            Person p = new Person("Enrique", "Av. Gualberto Villaroel", dateTime);
            return p;
        }

        internal static Person CreatePersonWithInvalidName(string name)
        {
            Person p = new Person(name, "Av. Gualberto Villaroel", DateTime.Now.AddYears(-25));
            return p;
        }
    }

Si observamos el codigo de las pruebas puede parecer ordenado y con bastante sentido, sin embargo a medida que las pruebas van creciendo en complejidad, la clase Helper que no es otra cosa que un Factory, va saliendose de nuestras manos, porque se van creando metodos que luego son mas complicados de mantener asi como metodos “residuales” que pronto no seran usados. ¿La solucion? hay varias, yo veo fundamentalmente dos:

Una usar constructores anonimos, de la siguiente manera:

[TestMethod]
        public void TestPersonInvalidAge()
        {
            //Person enrique = PersonObjectMother.CreatePersonWithInvalidDate(new DateTime(1725, 1, 1));
            Person enrique = new Person
            {
                Address = "Av. Gualberto Villaroel",
                Name = "Enrique",
                DateOfBirth = new DateTime(1725, 1, 1)
            };

            var age = (DateTime.Now.Year - enrique.DateOfBirth.Year);
            age = enrique.DateOfBirth > DateTime.Now.AddYears(-age) ? age - 1 : age;
            Assert.IsTrue(age > 1 && age <= 130, "Age must be between 1 and 130, currently is " + age);
        }

Parece tambien una buena idea, pero les aseguro que no lo es del todo, por que? Porque como observaran frecuentemente en la construccion de objetos, se necesita variar una o dos propiedades y dejar las restantes con valores “por defecto”, esto nos llevaria a duplicar bastante codigo, aqui quizá no es significativo pero pronto se convertiria tambien en un dolor de cabeza.

La otra solución, es usar Test Data Builder, una variacion personal que yo elegi, porque si ven las implementaciones mas formales, observaran que tambien se basan en metodos “fluent” para construir el objeto, pero aqui me valgo de los metodos anonimos para lograr un efecto mas “ordenado”.

[TestMethod]
        public void TestPersonInvalidAge()
        {
            //Person enrique = PersonObjectMother.CreatePersonWithInvalidDate(new DateTime(1725, 1, 1));
            //Person enrique = new Person
            //{
            //    Address = "Av. Gualberto Villaroel",
            //    Name = "Enrique",
            //    DateOfBirth = new DateTime(1725, 1, 1)
            //};
            Person enrique = new PersonTestDataBuilder
                                 {
                                     DateOfBirth = new DateTime(1725, 1, 1)
                                 }.Build();

            var age = (DateTime.Now.Year - enrique.DateOfBirth.Year);
            age = enrique.DateOfBirth > DateTime.Now.AddYears(-age) ? age - 1 : age;
            Assert.IsTrue(age > 1 && age <= 130, "Age must be between 1 and 130, currently is " + age);
        }

Como se observa, pareceria muy similar al uso de los metodos anonimos, solamente que aqui usamos un patron Factory, combinado con un constructor anonimo del Factory, esto nos permite enfocarnos solamente en la propiedad a la que queremos asignar el valor. Aqui el codigo del Test Data Builder:

public class PersonTestDataBuilder
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string Address { get; set; }

        public PersonTestDataBuilder()
        {
            Name = "Enrique";
            Address = "Av. Gualberto Villaroel";
            DateOfBirth = new DateTime(1984, 8, 15);
        }

        public Person Build()
        {
            //return new Person(Name, Address, DateOfBirth);
            return  new Person
                        {
                            Address = this.Address,
                            DateOfBirth = this.DateOfBirth,
                            Name = this.Name
                        };
        }
    }

Espero que les sea útil.

Saludos

Una respuesta a “TDD con el patrón Test Data Builder”

Deja un comentario

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