YoProgramo.NET Blog
Anotaciones de Programador

TDD: Test Driven Development

Definición de TDD:

TDD es una metodología de trabajo donde su principal área de trabajo son los Tests, el desarrollo de una aplicación basada en TDD consiste en  diseñar los tests y luego ir superándolos por medio del refactoreo de la aplicación.

Muchos también dicen que TDD es la suma de dos técnicas de programación: TFD y Refactorización. Con respecto a TFD, es la técnica que a muchos programadores les parecerá extraña y establece inicialmente una pauta que consiste en la creación de los tests primero antes que todo, inclusive antes de cualquier porción de código de nuestra aplicación.

Ciclos TDD:

En esta metodología existen pocos pero importantes pasos a seguir, dichos pasos forman el corazón de TDD y no deberíamos saltear ninguno de ellos.

tdd 

Crear un Test: Escribimos un código que representará a nuestro test o prueba para un determinado requerimiento a superar.

Creación de código y refactoreo: Este es uno de los puntos más importante de TDD, dado que en el es donde modelamos la aplicación basándonos en superar nuestras pruebas.

Ejecutar tests: Consiste en correr todos los tests y verificar que no hayan fallos. En caso de haberlos deberemos refactorizar para lograr pasar las pruebas satisfactoriamente.

Crear nuevos test: El desarrollo continúa y agregamos mas tests para dar comienzo otra vez a todo el ciclo.

December 21, 2009 07:12 by fabianfigueredo
Categorías: TDD

TDD Falsos Repositorios para Servicios

Ayer, Mario y Ángel me señalaron algunos errores en mi post anterior, en realidad es posible evitar tener que escribir la clase FalsoPostRepositorio.

Aquí les muestro como quedo mi test Crear_post_Test() donde utilizo Callback de moq.

        [TestMethod()]
        public void Crear_post_Test()
        {
            Dictionary<long, IPost> posts = new Dictionary<long, IPost>();
            var falso_repositorio = new Mock<IPostRepositorio>();
            falso_repositorio
                .Setup(x => x.Agregar(It.IsAny<IPost>()))
                .Callback((IPost p) => posts.Add(p.Id, p));
            
            IPostServicio servicioPost = new PostServicio(falso_repositorio.Object);
            IPost post = new Post();
            post.Id = 1;
            post.Title = "Prueba";

            servicioPost.Crear(post);

            Assert.AreEqual(posts.Count, 1);
            Assert.AreEqual(posts[1], post);
        }

y el test que prueba la actualización seria el siguiente:

        [TestMethod()]
        public void Actualizar_post()
        {
            Dictionary<long, IPost> posts = new Dictionary<long, IPost>();
            IPost post = new Post();
            post.Id = 1;
            post.Title = "Prueba";
            posts.Add(1, post);
            var falso_repositorio = new Mock<IPostRepositorio>();
                falso_repositorio
                    .Setup(x => x.Actualizar(IsAny<IPost>()))
                    .Callback<IPost>((p) => posts.ActualizarPost(p));

            IPostServicio servicioPost = new PostServicio(falso_repositorio.Object);
            post = new Post();
            post.Id = 1;
            post.Title = "Prueba actualizada";

            servicioPost.Actualizar(post);

            Assert.AreEqual(posts[1].Title, "Prueba actualizada");
        }

ActualizarPost() es un método de extensión.

December 3, 2009 08:10 by fabianfigueredo
Categorías: C# | TDD

TDD diseñando Servicios y Repositorios

obreros

Estoy en medio de una *BIG REFACTORY* del código fuente de yoprogramo.net, pero el motivo de este post no es contarles eso si no comentarles como estoy realizando mis tests para diseñar Servicios y Repositorios.

Lo primero, tenemos que tener algo en claro, para programar bien #TDD es necesario conocer algunas cosas, que ya ayer Carlos Peix explico muy bien en su WebCast. Para que nuestra capa de servicios sea optima para TDD es necesario que apliquemos el patrón Inyección de Dependencias y que utilicemos algún contenedor IoC para trabajar con ellas.

Si no tienes conocimientos de estas herramientas y patrones te invito a que veas mi video tutorial sobre contenedores IoC.

Usare como ejemplo, algo del mundo real –copiándole a José- que aplique en yoprogramo.net para diseñar mis servicios.

Diseñando el servicio IPostServicio:

Este servicio estará en la capa infraestructura de la aplicación:

namespace YoProgramo.Nucleo
{
    public interface IPostServicio {
        IList TraerTodos();
        IList TraerTodosSinSpam();
        IList TraerPostsParaHome(int? top);
        IPost TraerPorId(long id);
        IList TraerPorTag(ITag tag);
        IList TraerPorUsuario(IUser usuario);
        IList TraerFavoritosPorUsuario(IUser usuario);
        IList TraerTodosLosPostsSinRespuestas();
        IList GetByCategory(ICategoria categoria);
        IPost TraerPorSlug(string slug);
        void Crear(IPost obj);
        void Actualizar(IPost obj);
        void Borrar(IPost obj);
    }
}

Luego de crear la interface, empecé con el diseño y creación de los tests. El primero que les mostraré es uno para probar el método TraerTodos(). Es necesario utilizar alguna librería de mock *yo utilizo moq* para poder mockear el repositorio que utiliza el servicio.

Se dice que estos tests son de diseño, porque en realidad la clase PostServicio no existe y luego de crear el test iremos refactorizando hasta lograr tener terminada la clase con todos sus métodos funcionando.

namespace YoProgramo.Infraestructura.Tests
{
    [TestClass()]
    public class PostServicioTest
    {
        public IList HacerListaDePosts()
        {
            var lista = new List();
            IPost post = new Post();
            lista.Add(post);
            post = new Post();
            lista.Add(post);
            return lista;
        }

        [TestMethod()]
        public void Traer_todos_y_comprobar_que_no_sea_nulo_y_que_sean_2()
        {
            var falso_repositorio = new Mock();
            falso_repositorio
                .Setup(x => x.TraerTodos()).Returns(HacerListaDePosts());
            IPostServicio servicioPost = new PostServicio(falso_repositorio.Object);

            var list_de_posts = servicioPost.TraerTodos();

            Assert.IsNotNull(list_de_posts);
            Assert.AreEqual(list_de_posts.Count(), 2);            
        }
    }
}

Primero creamos un mock – un mock es un objeto proxy – para así luego modificar su comportamiento, en este caso indicamos que al llamar al método TraerTodos() retorne una lista de objetos Post. Luego este objeto mock lo utilizamos para enviarlo al servicio a través de su constructor.

Refactoring, refactoring…

Al terminar el test el mismo falla y aquí es donde comienza el trabajo verdadero en TDD, hay que refactorizar para lograr superar nuestras pruebas.

Mi clase PostServicio luego del refactoreo:

namespace YoProgramo.Infraestructura.Servicios
{
    public class PostServicio : IPostServicio
    {
        protected IPostRepositorio _postRepositorio;

        public PostServicio(IPostRepositorio postRepositorio)
        {
            _postRepositorio = postRepositorio;
        }

        #region IPostServicio Members

        public IList TraerTodos()
        {
            return this._postRepositorio.TraerTodos();
        }

        public IList TraerTodosSinSpam()
        {
            throw new NotImplementedException();
        }

        public IList TraerPostsParaHome(int? top)
        {
            throw new NotImplementedException();
        }

        public IList TraerPorTag(ITag tag)
        {
            throw new NotImplementedException();
        }

        public IList TraerPorUsuario(IUser user)
        {
            throw new NotImplementedException();
        }

        public IList TraerFavoritosPorUsuario(IUser user)
        {
            throw new NotImplementedException();
        }

        public IList TraerTodosLosPostsSinRespuestas()
        {
            throw new NotImplementedException();
        }

        public IList GetByCategory(ICategoria categoria)
        {
            throw new NotImplementedException();
        }

        public IPost TraerPorSlug(string slug)
        {
            throw new NotImplementedException();
        }

        public void Crear(IPost obj)
        {
            _postRepositorio.Agregar(obj);            
        }

        public void Actualizar(IPost obj)
        {
            _postRepositorio.Actualizar(obj);            
        }

        public void Borrar(IPost obj)
        {
            _postRepositorio.Borrar(obj);  
        }

        public IPost TraerPorId(long id)
        {
            return _postRepositorio.Get(id);
        }

        #endregion
    }
}

Solo implemente los métodos que utilizo para estas pruebas especificas, los demás serán para otros tests.

Falsos repositorios:

No siempre podemos mockear, en ocasiones tendremos que diseñar falsos objetos a pata, lo feo de esto es que este código es solo para las pruebas, no se usa en otra parte. Puede hacernos perder tiempo pero es necesario para que nuestras pruebas sean óptimas. Ahora veamos este test…

        [TestMethod()]
        public void Actualizar_post()
        {
            var falso_repositorio = new FalsoPostRepositorio();
            IPostServicio servicioPost = new PostServicio(falso_repositorio);
            IPost post = new Post();
            post.Id = 1;
            post.Title = "Prueba";

            servicioPost.Crear(post);
            IPost postParaActualizar = servicioPost.TraerPorId(1);
            postParaActualizar.Title = "Prueba actualizada";
            servicioPost.Actualizar(postParaActualizar);

            IPost postEsperado = servicioPost.TraerPorId(1);

            Assert.IsNotNull(postEsperado);
            Assert.AreEqual(postEsperado.Title, "Prueba actualizada");
        }

Como ven, aquí no utilizo mock dado que para actualizar objetos necesitamos algo de interacción con el repositorio, de hecho estamos realizando dos pasos, uno para crear un objeto y otro para modificarlo.

Mi Falso Repositorio:

namespace YoProgramo.Infraestructura.Tests.FalsosRepositorios
{
    public class FalsoPostRepositorio : IPostRepositorio
    {
        private Dictionary<long, IPost> posts;

        public FalsoPostRepositorio()
        {
            posts = new Dictionary<long, IPost>;
        }


        #region IRepositorio Members

        public IPost Get(long id)
        {
            return posts[id];
        }

        public IList TraerTodos()
        {
            throw new NotImplementedException();
        }

        public void Borrar(IPost entidad)
        {
            posts.Remove(entidad.Id);
        }

        public void Agregar(IPost entidad)
        {
            posts.Add(entidad.Id ,entidad);
        }

        public void Actualizar(IPost entidad)
        {
            posts[entidad.Id] = entidad;
        }

        public IList FindAll()
        {
            throw new NotImplementedException();
        }

        public IList FindAll(System.Linq.Expressions.Expression> expression)
        {
            throw new NotImplementedException();
        }

        public IQueryable Find()
        {
            throw new NotImplementedException();
        }

        public IQueryable Find(long id)
        {
            throw new NotImplementedException();
        }

        public IQueryable Find(System.Linq.Expressions.Expression> expression)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Y esto es todo por hoy, nada más que decir sobre el tema. Con el webcast de ayer me entusiasme mas con esto y ahora ya es tarde para escaparme jeje.

December 2, 2009 08:45 by fabianfigueredo
Categorías: C# | TDD