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

Perdiendo mis twitter posts?

Este post es realmente corto y probablemente no falte alguno que me diga que no tiene nada que ver con desarrollo, pero bueno quizá sí.

Desde hace un buen tiempo digamos unos 6 u 8 meses atrás me he vuelto un twitter user mas asiduo de lo que era antes y me vino la curiosidad (quien sabe porque?) de preguntarme que pasan con mis twitter posts, aunque realmente no hice nada para averiguarlo, hasta que me entro la necesidad y no solo la curiosidad, de obtener unos posts que hice hace algunos meses, porque deben saber que yo uso mi twitter más que como un lugar para jactarme de mis conocimientos o mostrárselos a la mayor cantidad de personas, como un lugar donde coloco todo lo que voy leyendo, investigando y curioseando diariamente y tener ese espacio como una especie de bitácora, en lo que se refiere fundamentalmente a mi gran pasion, el desarrollar software.

image

Pero menudos sustos me lleve al enterarme que primero, twitter solo almacena los 3200 últimos posts que hicieron (aqui mas informacion sobre este tema), afortunadamente yo voy solo por los 300, pero ya es el 10%, esto me comenzo a preocupar, sumado a que me puse a querer recuperar mis posts con la misma herramienta con la que los hago, actualmente uso una de las mejorcillas según yo, es twhirl, lo sé lo sé, está construida con Adobe Air, pero Uds. me recomendaran otra que quizá esté basada en .NET, aunque debo decir que también pase unas buenas horas buscando una para postear y la mejor es twhirl, insisto para mí; bueno me estoy saliendo del tema, que es que definitivamente no podía encontrar mis antiguos posts en Twitter.

Buscando me encontré con este artículo que hizo crecer enormemente mis temores de haber perdido definitivamente mis post antiguos de twitter, por más que en la página oficial digan que puedo recuperar los últimos 3200, no sería la primera vez que algún fabricante promete que su producto hace algo, cuando en realidad no lo hace. En el artículo se menciona una expiración de los posts, nuevamente un susto y no porque el post que buscaba era lo más importante del mundo, sino porque todos los post que hice sumados representan meses de anotar metódicamente cosas importantes. Pero como verán en ese post aparte de mencionar el problema, muestra 10 posibles soluciones para respaldar nuestros post de twitter. Luego de probar todas ellas debo indicar que recomiendo las siguientes dos:

The archivist, que no es nada más ni nada menos que una aplicación WinForms, con un instalador ClickOnce, en si la herramienta recupera, si bien no todos sus posts, la mayoria un gran plus que tiene es la posibilidad de exportacion a excel y el analisis de los datos recuperados de Twitter, por usuario.

clip_image001

Tweetake, que es una aplicación twitter web, esta es la que se lleva la flor, porque efectivamente recupero la totalidad de mis posts, y no solo permite recuperarlos, sino incluso realizar backups de toda la configuración de twitter, favoritos, seguidores, amigos, etc. y exportar todo a un archivo delimitado por comas, que obviamente puede ser abierto por Excel.

clip_image002

Como supondrán correctamente, seguiré haciendo mis posts en Twitter, esperando que un día ellos implementen la recuperación de todos mis posts, como lo prometen, sin ninguna restricción, mientras seguiré usando Tweetake y secundariamente The Archivist para respaldar regularmente mi bitácora en la web.

Saludos