Codigo · KATAS

KATA TDD HARRY POTTER

Buenas,

al igual que en los dos post anteriores seguimos con una kata facilita aplicando TDD (Test-Driven Depelopment). La kata ha sido realizada en VS2010 (Framework 4.0) y para la realización de los test hemos utilizado al igual que en las ocasiones anteriores:

  • Nunit (Framework Open Source de pruebas de unidad para Microsoft .NET).
  • NCrunch (Ejecuta pruebas unitarias en linea).

Enunciado del problema Harry Potter:

Harry Potter es una colección de 7 novelas fantásticas escrita por la autora británica J. K. Rowling, en la que se describen las aventuras del joven aprendiz de mago Harry Potter y sus amigos. Nuestra librería ha decidido poner en venta los libros con una oferta especial:

Cada libro cuesta 8€
Si compras 2 libros diferentes tienes un 5% de descuento
si compras 3 libros diferentes tienes un 10% de descuento
si compras 4 libros diferentes tienes un 15% de descuento
si compras 5 libros diferentes tienes un 20% de descuento
si compras 6 libros diferentes tienes un 30% de descuento
si compras toda la colección tienes un 45% de descuento

Tras la locura desatada por semejante oferta, la librería se está llenando de adolescentes alocados comprando libros en grandes cantidades y las cajeras no dan a basto porque pierden mucho tiempo realizando cálculos.

Por lo tanto tu objetivo es crear un módulo de código fuente que pueda aportar la solución a este problema y devuelva el precio de compra para cada combinación que haga cualquier cliente.

Se proponen algunos ejemplos prácticos para entender los diferentes casos:

comprando 2 copias del primer libro el precio es 8€ por unidad: 8 * 2 = 16
comprando 2 copias del primer libro y una del segundo: (8€ * 2libros * 0.95descuento) + 8€
Comprando 5 copias de toda la colección: (8€ * 7libros * 0.55descuento) * 5copias

Basada en la kata Potter: http://codingdojo.org/cgi-bin/wiki.pl?KataPotter

Solución Harry Potter: Como no comentan nada al respecto de la forma en la cual vamos a recibir los datos para calcular el precio de los libros que compre el cliente yo he asumido que el metodo DamePrecio va a devolver un valor del tipo Double y va a recibir una matriz bidimensional de enteros de la siguiente forma:

Donde la fila libros solo puede tomar valores:

  • 0: No compramos libro.
  • 1: Compramos libro/s.

La solución quedaría de la siguiente forma:

  • KataPotterTest: librería en la cual vamos escribiendo las pruebas.
  • KataPotter: librería en la cual vamos escribiendo el código para calcular el precio de la cesta de libros comprados por el cliente.

A continuación mostramos el contenido de las clases una vez pasados todos los test y refactorizado.

KataPotterTest.cs:

using NUnit.Framework;

namespace KataPotterTest
{
    public class KataPotterTest
    {
        [TestFixture]
        public class No_Compramos_Nada
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido =
                new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Cero()
            {
                Assert.AreEqual(resultado, 0);
            }
        }

        [TestFixture]
        public class Compramos_Un_Libro
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            private double resultado2;
            private double resultado3;

            int[,] pedido = new int[,] { { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido2 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido3 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 1 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
                resultado2 = SUT.DamePrecio(pedido2);
                resultado3 = SUT.DamePrecio(pedido3);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8);
                Assert.AreEqual(resultado2, 8);
                Assert.AreEqual(resultado3, 8);
            }
        }

        [TestFixture]
        public class Compramos_Dos_O_Mas_Libros_Iguales
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            private double resultado2;
            private double resultado3;

            int[,] pedido = new int[,] { { 1, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido2 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 4 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido3 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 5 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
                resultado2 = SUT.DamePrecio(pedido2);
                resultado3 = SUT.DamePrecio(pedido3);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 3);
                Assert.AreEqual(resultado2, 8 * 4);
                Assert.AreEqual(resultado3, 8 * 5);
            }
        }

        [TestFixture]
        public class Compramos_Dos_Libros_De_Un_Tipo_Y_Uno_De_Otro
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            private double resultado2;
            private double resultado3;

            int[,] pedido = new int[,] { { 1, 2 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido2 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 2 }, { 0, 0 }, { 1, 1 } };
            int[,] pedido3 = new int[,] { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 1, 2 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
                resultado2 = SUT.DamePrecio(pedido2);
                resultado3 = SUT.DamePrecio(pedido3);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, (8 * 2 * 0.95) + 8);
                Assert.AreEqual(resultado2, (8 * 2 * 0.95) + 8);
                Assert.AreEqual(resultado3, (8 * 2 * 0.95) + 8);
            }
        }

        [TestFixture]
        public class Compramos_Tres_Libros_Distintos
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            private double resultado2;
            private double resultado3;

            int[,] pedido = new int[,] { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido2 = new int[,] { { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
            int[,] pedido3 = new int[,] { { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 0 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
                resultado2 = SUT.DamePrecio(pedido2);
                resultado3 = SUT.DamePrecio(pedido3);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 3 * 0.90);
                Assert.AreEqual(resultado2, 8 * 3 * 0.90);
                Assert.AreEqual(resultado3, 8 * 3 * 0.90);
            }
        }

        [TestFixture]
        public class Compramos_Cuatro_Libros_Distintos
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido = new int[,] { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 4 * 0.85);
            }
        }

        [TestFixture]
        public class Compramos_Cinco_Libros_Distintos
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido = new int[,] { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 1, 1 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 5 * 0.80);
            }
        }

        [TestFixture]
        public class Compramos_Seis_Libros_Distintos
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido = new int[,] { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 6 * 0.70);
            }
        }

        [TestFixture]
        public class Compramos_La_Coleccion
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido = new int[,] { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado, 8 * 7 * 0.55);
            }
        }

        public class Compramos_7_Del_1_6_Del_2_5_Del_3_4_Del_4_3_Del_5_2_Del_6_1_Del_7
        {
            KataPotter.IKataPotter SUT = new KataPotter.KataPotter();
            private double resultado;
            int[,] pedido = new int[,] { { 1, 7 }, { 1, 6 }, { 1, 5 }, { 1, 4 }, { 1, 3 }, { 1, 2 }, { 1, 1 } };

            [SetUp]
            public void Setup()
            {
                resultado = SUT.DamePrecio(pedido);
            }

            [Test]
            public void Devuelve_Precio()
            {
                Assert.AreEqual(resultado,
                    (8 * 7 * 0.55) + (8 * 6 * 0.70) + (8 * 5 * 0.80) +
                    (8 * 4 * 0.85) + (8 * 3 * 0.90) + (8 * 2 * 0.95) + (8 * 1 * 1));
            }
        }
    }
}

KataPotter.cs:

using System.Collections.Generic;
using System.Linq;

namespace KataPotter
{
    public class KataPotter : IKataPotter
    {
        const int PRECIO_LIBRO = 8;
        const int COLECCION_POTTER = 7;
        const double DESCUENTO_2 = 0.05;
        const double DESCUENTO_3 = 0.10;
        const double DESCUENTO_4 = 0.15;
        const double DESCUENTO_5 = 0.20;
        const double DESCUENTO_6 = 0.30;
        const double DESCUENTO_7 = 0.45;

        public double DamePrecio(int[,] pedido) {
            int tamanioCarro = 0;
            double precioFinal = 0;
            double precioPaquete = 0;
            double descuento = 0;

            List<int> carro = LlenaCarroDesdePedido(pedido);

            while (ExistenElementosEnCarro(carro)) {
                descuento = DameDescuento(carro);
                precioPaquete = DamePrecioPaquete(carro, descuento);
                precioFinal = precioFinal + precioPaquete;
                tamanioCarro = carro.Count;
                EliminaElementosPagados(carro, tamanioCarro);
            }

            return precioFinal;
        }

        private void EliminaElementosPagados(List<int> carro, int tamanioCarro)
        {
            for (int i = tamanioCarro - 1; i >= 0; i--)
            {
                if (carro[i] == 1)
                    carro.Remove(carro[i]);
                else
                    carro[i]--;
            }
        }

        private double DamePrecioPaquete(List<int> carro, double descuento)
        {
            return PRECIO_LIBRO * carro.Count() * (1 - descuento);
        }

        private double DameDescuento(List<int> carro)
        {
            switch (carro.Count)
            {
                case 7:
                    return DESCUENTO_7;
                case 6:
                    return DESCUENTO_6;
                case 5:
                    return DESCUENTO_5;
                case 4:
                    return DESCUENTO_4;
                case 3:
                    return DESCUENTO_3;
                case 2:
                    return DESCUENTO_2;
                default:
                    return 0;
            }
        }

        private bool ExistenElementosEnCarro(List<int> carro)
        {
            return carro.Count > 0;
        }

        private List<int> LlenaCarroDesdePedido(int[,] pedido)
        {
            List<int> carro = new List<int>();
            for (int i = 0; i < COLECCION_POTTER; i++)
            {
                if (pedido[i, 0] == 1)
                {
                    carro.Add(pedido[i, 1]);
                }
            }
            return carro;
        }
    }
}

IKataPotter.cs:

namespace KataPotter
{
    public interface IKataPotter
    {
        double DamePrecio(int[,] pedido);
    }
}

También he subido la solución a Github:

https://github.com/joaqfer/KATA_TDD_POTTER.git

Para aquellos que lo desconozcan Github es una plataforma de desarrollo colaborativo de software para alojar proyectos utilizando el sistema de control de versiones Git.

En el siguiente post intentaré detallar lo más claro posible los pasos a seguir para configurar Git y Github en VS2010.

Un saludo,

Deja un comentario