El valor de TDD (3)

Anterior Post

En el primer post de esta serie, ya mencioné el tema simplicidad, y di algún ejemplo. Es un tema fundamental para entender TDD (Test-Driven Development), pero parece ser que es uno de los menos entendidos. Así que me permito insistir hoy en el tema.

Una de las cosas hacia las que empuje TDD es a la simplicidad, pero siempre y cuando pensemos alineados con el flujo de trabajo de TDD. Veamos la respuesta a la pregunta:

¿Por qué TDD empuja a la simplicidad?

Por dos razones: como se escriben los tests primero que el código (y muchas veces, se escribe un solo test, y hasta que no se termina de trabajar en ese test, no se pasa a otro), nos conviene comenzar con tests sencillos de expresar su intención, no comenzar por los tests más complicados. Como ejemplo, pueden ver los commits de mi proyecto SparkSharp (ver posts). Ahí tengo que implementar un concepto llamado Dataset, sobre el que aplicar map y reduce. ¿Acaso escribí los tests más complicados primero? No, siguiendo la premisa de ir por pasos de bebé (“baby steps”), fui planteando los casos más sencillos primero. Por ejemplo, escribí primero tests para Datasets que se alimentan de items enumerables en memoria, antes de pasar a Datasets que se alimentan de archivos locales. Y luego, en algún momento futuro, escribiré y haré pasar a verde tests sobre Datasets que se alimenten de datos remotos. Pero como en la vida, el más largo camino comienza con un solo paso. Y otro paso y otro paso y así.

¿Acaso en ese proyecto implementé directamente la API del proyecto original, Apache Spark? No, la idea es ir planteando casos de uso pequeños de la API, dado un estado, llamar a un método, y escribir lo que se espera obtener como respuesta o como cambio de estado. Por ejemplo, en el proyecto original los Datasets se crean con métodos factoría de un Spark Context. Yo no he tenido necesidad de eso en los tests simples que he escrito. Cuando llegue el caso de necesitar eso, por alguna circunstancia concreta, lo haré, haciendo refactor y rediseño. Pero recuernde: no hay que cruzar el puente ANTES de llegar al puente.

Y la segunda razón: TDD nos lleva a resolver el pasaje del test a verde DE LA FORMA MAS SENCILLA. Eso lo tenemos que poner nosotros, la voluntad de implementar código de producción, APENAS PARA que pase el test. Si sentimos que al código de producción le falta algo (por ejemplo “debería controlar que este parámetro no sea nulo”), entonces ESCRIBIMOS el test correspondiente, y luego agregamos la funcionalidad que falta. Si no hay ningún test que pida a gritos la implementación de algo, ese algo NO SE IMPLEMENTA. Primero el test, luego la implementación. De alguna forma, esto lleva a que casi cada línea de código de producción PUEDA SER TRAZADO su origen a algún test. Línea de producción que no nace de un test, es sospechosa.

Corolario: no agrego librerías y dependencias que no necesito para resolver un test. No digo “no agregar Angular”, o “no agregar Hibernate” nunca. Digo: lo agrego cuando realmente los casos de uso planteados (los casos de la API, lo que espero que haga), realmente justifican el agregado de la dependencia. Y van a ver, que gracias a TDD, el agregado de la librería en general no cuesta mucho. Una de las consecuencias del uso de TDD y simplicidad y lo ágil en general, es que el diferir las decisiones no tiene un gran costo, y hasta mejora nuestro diseño porque permite que lo que se agrega se agregue por necesidad real, y no por “cargo cult programming” o porque sea “la mejor práctica”. Recuerden, no hay “mejores prácticas”, sino “mejores prácticas en contexto”. Además, el diferir la decisión tal vez ayudar a decidir, luego de ver mejor los casos de uso, que en vez de Angular necesitamos React, o que en vez de un ORM necesitamos un MicroORM, y así. Permite que nuestro diseño no vaya anclado a una decisión temprana de tecnología.

Veamos ahora un “pecado” que he observado cuando se intenta comenzar con TDD: pensar demasiado por adelantado. Recuerden, los primeros tests tienen que ser sencillos. Por ejemplo, en mi implementación de una especie de OLAP en memoria, MemOlap, ¿acaso pensé todas las estructuras internas de antemano? Para nada. A medida que implementé casos de uso, fui implementado mejor lo que quería hacer, y refactorizando. El software crece así como un organismo, guiado por nuestras ideas y por el ambiente que lo rodea, los tests que vamos incorporando de a poco. Justamente, MemStore fue la semilla para un proyecto real no público, que avanzó mucho más, y hoy está en producción. Y cuando en ese avance se plantearon nuevos casos de uso, o se vió que había que refactorizar algo, se lo hizo, sin mayor esfuerzo. Doy un caso: cuando se llegó a probar el rendimiento con cientos de millones de registros, se vió que algunas operaciones eran lentas. Ahí se hizo refactor, se implementó el recorrido de los items de una forma más eficientes, sin apelar a crear y destruir objetos a cada momento, y gracias a los tests en verde, todo siguió andando. Hasta hubo refactor quirúrgico: llamo así a los que cambian mucho de la implementación. Y al comienzo de tal tipo de refactor pueden quedar muchos tests en rojo. Pero volviendo todo a verde, al final queda un sistema tan estable como al principio.

En gran parte, el bajo esfuerzo de cambio resultó de la simplicidad adoptada hasta ese momento, y la ayuda de los tests ya escritos, que permitieron rápidamente, ante un refactor (cambio interno de implementación) o un rediseño (cambio de la API porque hay nuevos casos de uso a contemplar), darse cuenta en el momento qué se había roto y qué no.

Entonces, no comenzar a pensar ya en el primer test en repositorios, ORM, y contextos de ejecución. Hay que relajarse y volver a plantear el desarrollo del software como juego. Imaginemos que cuanto más tests en verde tengamos, más avanzamos en el juego.

Hacer de cada test, un test sencillo.

Hacer de cada implementación, la más simple para que pase el test.

No agregar algo a la implementación, sin respaldarlo con test.

Y como siempre, la neurona atenta, vermú con papas fritas y good show! 😉

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Desarrollo Agil, Programacion, Test-Driven Development | Comments Off on El valor de TDD (3)

SparkSharp, Spark in C# (2) Implementando Map y Reduce

Anterior Post

El proyecto está en:

https://github.com/ajlopez/SparkSharp

Como es usual, lo estoy desarrollando siguiendo el flujo de TDD (Test-Driven Development), así que el código va evolucionando a medida que encuentro nuevas soluciones a pequeños casos de uso planteados por los tests. Lo que muestro hoy puede cambiar mañana, al necesitarse en nuevos tests nueva funcionalidad o nuevas implementaciones. Por ejemplo, en el proyecto original de Apache Spark los dataset se crean desde métodos factorías en algo llamado Spark Context. Pero esa funcionalidad todavía no la necesité. Así que por ahora mis tests apuntan a crear simples objetos Dataset, directamente con el operador new, y a consumirlos.

Actualmente, nacida en algún refactor, está la clase abstracta BaseDataset. Parte del código:

public abstract class BaseDataset<T> : IEnumerable<T>
{
    public abstract IEnumerable<T> Elements { get; }

    public BaseDataset<S> Map<S>(Func<T, S> map)
    {
        return new EnumDataset<S>(this.ApplyMap(map));
    }

    public S Reduce<S>(Func<S, T, S> reduce)
    {
        S result = default(S);

        foreach (var elem in this)
            result = reduce(result, elem);

        return result;
    }
    
    // ...

    private IEnumerable<S> ApplyMap<S>(Func<T, S> map)
    {
        foreach (var elem in this)
            yield return map(elem);
    }
    
    // ...
}

Vemos que la implementación de enumerar los elementos que contiene queda delegada a la clase concreta. Pero en el código de arriba dejé la implementación base de los métodos Map y Reduce. Gracias a C#, estos métodos pueden recibir un lamba, o una Func (una función como objeto) como veremos en algún test.

Vean como el ApplyMap usa el yield de C# para devolver cada elemento, y el foreach sólo se vuelve a ejecutar cuando el consumidor del IEnumerable necesita el próximo item. Tanto el uso de lambdas como de yield han simplificado la implementación de estas ideas. Esta es una prueba de lo que se necesita en un lenguaje para cumplir mejor con alguna necesidad. Como digresión, comento que me parece que la historia de C# ha sido bastante acertada, incorporando estas ideas, mientras que en el mundo Java, se ha dado el caso de lenguajes como Scala que, siendo un gran lenguaje, me parece que trata de sumar demasiadas cosas.

No se ejecutan tests sobre la clase abstracta (que de nuevo, vean la historia de commits, nació como un refactor), sino sobre alguna concreta. Siendo EnumDataset una clase concreta (a examinar en próximos posts), sea un test típico del Map:

[TestMethod]
public void MapIncrement()
{
    EnumDataset<int> ds = new EnumDataset<int>(new int[] { 1, 2, 3 });
    BaseDataset<int> mapds = ds.Map(i => i + 1);
    var enumerator = mapds.GetEnumerator();

    for (int k = 1; enumerator.MoveNext(); k++)
        Assert.AreEqual(k + 1, enumerator.Current);

    Assert.AreEqual(3, mapds.Count());
}

Y un test del Reduce:

[TestMethod]
public void ReduceSum()
{
    EnumDataset<int> ds = new EnumDataset<int>(new int[] { 1, 2, 3 });
    var result = ds.Reduce<int>((x, y) => x + y);

    Assert.IsNotNull(result);
    Assert.AreEqual(6, result);
}

Próximos temas: más métodos de BaseDataset, datasets concretos, datasets con clave, etc…

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in .NET, C#, Computación Distribuida, Proyectos Open Source, SparkSharp, Test-Driven Development | Comments Off on SparkSharp, Spark in C# (2) Implementando Map y Reduce

SparkSharp, Spark in C# (1) Primeras ideas

Siguiente Post

En el post de ayer mencionaba el uso de Spark por parte de gente de Medallia. Conocía un poco de ese proyecto Apache, pero hoy me puse a ver la interfaz de programación que tienen, y me pareció interesante reproducir alguna parte en C#.

Ellos trabajan con Dataset, conjuntos de datos que pueden venir, por ejemplo, de archivos locales o distribuidos, y a los que aplican funciones de transformación (Map) y funciones de reducción, obtención de algún valor resultante (Reduce). Los trabajos definidos se pueden correr en varios nodos (tengo que revisar cómo consiguen consolidar la etapa de Reduce final).

Pero para comenzar una implementación en C#, me parece interesante comenzar en pequeño, y como es usual, usar el flujo de trabajo de TDD (Test-Driven Development). Así que ni lerdo ni perezoso, en la tarde de ayer comencé este proyecto:

https://github.com/ajlopez/SparkSharp

Si ven los primeros commits, se siguió la idea de flujo de TDD. Va quedando que los datasets implementan como base a un IEnumerable<T> sobre el que se aplican de forma ordenada funciones Map, Reduce, Split, Take, Skip y otras que vayan apareciendo. Esos datasets pueden ser simples “wrappers” de otros IEnumerable<T> (como un arreglo de tipo T, o una lista), o pueden venir de tomar un texto y partirlo en líneas (ver el TextDataset), o tomar un archivo y procesarlo en líneas.

Todos esos datasets son, digamos, locales, no distribuidos. El próximo paso simple (siempre hay que ir por lo más simple) es exponer un dataset cualquiera para que se pueda consumir ordenadamente de forma remota. Por ejemplo, en un nodo/máquina podemos tener un gran archivo de texto a analizar. Queremos procesar sus líneas. Para esto hoy ya en el proyecto está el TextFileDataset, que procesa las líneas a medida que se van leyendo. Pero se podría implementar un ServerDataset o RestDataset, que sea un “wrapper” sobre ese dataset local, y se exponga para afuera, mediante TCP o una API que devuelva JSON o un simple string via HTTP. Entonces, distintas clases clientes (me imagino RestClientDataset, o ServerClientDataset), podrán consumir esos datos desde nodos remotos, como si fueran datasets locales. En el caso normal, un TextFileDataset expondría sus líneas a los nodos remotos, para que se puedan consumir, pero de una forma controlada: cada línea iría al próximo nodo que pida un item del dataset.

Despues de implementar la exposición de un dataset local como remoto (con ese cuidado de que cada item SOLO vaya a un nodo solicitante, que no se REPITA el proceso de un item entregándolo a DOS o más nodos), implementar la serialización/deserialización (temas ya encarados en AjErl y Aktores), automáticamente comenzamos a tener procesamiento distribuido. Claro que todo esto es el caso feliz: si el proyecto progresa, habrá que contemplar fallas en la comunicación, ingreso y egreso de nodos dinámicamente en el medio del proceso, coordinación/recolección de datos en un Reduce final que consuma resultados parciales de varios nodos, etc.

Pero piano, piano se va a lontano. Por ahora, baby steps, la neurona atenta, vermú con papas fritas, y good show!

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in C#, Computación Distribuida, Proyectos Open Source, SparkSharp | Comments Off on SparkSharp, Spark in C# (1) Primeras ideas

Tech Night en Medallia

El jueves pasado se organizó una reunión en Medallia, Argentina, en Buenos Aires. Pueden ver la empresa global en:

http://www.medallia.com/

y aparte de servicios, tienen un producto donde conectan a una empresa con sus clientes. Eso implica que tienen que trabajar con grandes volúmenes de datos, analizarlos, descubrir patrones y relaciones y hacer análisis de “sentimientos”. Tienen central en Palo Alto California, y desarrollan allá y en Buenos Aires. En otras ciudades, como Londres, tiene soporte, ventas, marketing y esas ‘)cosas que tienen que hacer los mortales que no programan ni se dedican a las matemáticas :-)

No fue una reunión abierta, sino por invitación. Habremos sido como treinta personas, del ámbito del desarrollo de software principalmente, que asistimos y disfrutamos de CERVEZA (sí, ya saben que si voy a alguna reunión no es por el tema, sino por la cerveza ja ja ja), y tragos de bebidas espirituosas (con nombres como C#, Java, y hasta NodeJS, no recuerdo los ingredientes, pero había tequila, jugos de fruta y similares). Luego de la charla técnica que hubo, sé que se zamparon varias pizzas y vieron un evento por televisión, algo así como fútbol, otra de esas cosas que hacen los mortales jaja.

Yo fui invitado gracias a la recomendación inicial de @gabrielsz que está trabajando desde hace unos años en Medallia, desde Palo Alto.

En la charla técnica, presentaron dos desarrolladores de Medallia:

https://www.linkedin.com/in/kreide Kristian Eide
https://www.linkedin.com/in/slicer Thorvald Natvig

La presentación giró alrededor de dos temas: el proceso de datos estructurados, y de datos desestructurados. Conversando con Eide, ví que trabajan mucho con Java, y con Spark, para el procesamiento distribuido (abandonaron Hadoop hace un tiempo parece). También trataron el tema de compilar “queries” sobre los grandes conjuntos de datos que tienen, pero tendría que revisar las tecnologías involucradas. Hay que ver los proyectos públicos de Medallia:

https://github.com/medallia

Es interesante ver el trabajo de

https://github.com/medallia/Word2VecJava

Donde portaron a Java un procesamiento de palabras a vectores, ver los papers relacionados

http://ttic.uchicago.edu/~haotang/speech/1301.3781.pdf
http://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf
http://www-personal.umich.edu/~ronxin/pdf/w2vexp.pdf

Jaja Ideas para portar a C# entonces acá, como Code Kata. Es interesante ver redes neuronales aplicadas en este problema (un tema con el que me encontré hace alredecor de tres décadas y sigue dando vueltas por ahí, van a aparecer en mi serie sobre JavaScript e Inteligencia Artificial y ya apareció en mi GitHub). Hay mucho de “sentiment analysis” al parecer en el trabajo de Medallia, igual comenté que hay poco de real semántica en estos caminos, parece más una correlación de textos que un real modelo del mundo y de la mente. Algo así comenté al final de mi charla en la JSConf Argentina 2014: todos estos algoritmos para reconocer gatos, reconocen miles de gatos, pero no tienen la más pálida idea de qué es un gato. Redes neuronales tiene esa fascinación: produce resultados, y eso es importante, pero no sé si no nos estamos perdiendo algo importante. O quizás sea el camino para una inteligencia artificial fuerte emergente, sólo que hasta ahora estamos en los primeros pasos.

La idea es que Medallia conecte con la comunidad de desarrollo local, y surjan ideas para realizar, dentro del grupo. Ya se formó un Google Groups, y en unos meses se realiza la próxima reunión (más cerveza :-)

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com

Posted in Buenos Aires, Inteligencia Artificial, Java, Programacion, Redes Neuronales, Reunion | Comments Off on Tech Night en Medallia

AjErl Implementando Erlang en C# (2) Expresiones

Anterior Post

Hace un tiempo que escribo de este proyecto, que trata de implementar Erlang como intérprete sobre C#. Veamos hoy una primera aproximación a expresiones. Una expresión es algo que se evalúa dentro de un contexto. Por ejemplo, la variable X, es una expresión que se evalúa dentro de un contexto que diga cuánto vale la variable X, a qué valor está ligada, o que diga que X todavía es una variable sin ligar.

Todas expresiones se escribieron usando el flujo de trabajo de TDD (Test-Driven Development). Quedó, con el tiempo, definida esta interfaz, que al final implementan todas las expresiones:

public interface IExpression
{
    object Evaluate(Context context, bool withvars = false);

    bool HasVariable();
}

El gran método es el evaluar, que puede admitir o no que queden variables sin ligar. También hay un método hasVariable() que indica si una expresión tiene o no una variable. Veamos la expresión más simple, una constante:

public class ConstantExpression : IExpression
{
    private object value;

    public ConstantExpression(object value)
    {
        this.value = value;
    }

    public object Value { get { return this.value; } }

    public object Evaluate(Context context, bool withvars = false)
    {
        return this.value;
    }

    public bool HasVariable()
    {
        return false;
    }
}

El método Evaluate no usa el Context provisto, sino que simplemente devuelve el objeto constante que se proveyó en el constructor al armar esta expresión (todavía no lo vimos, pero las expresiones se agrupan en un árbol de expresiones, que se evalúa recorriéndo sus ramas y hojas).

Una implementación menos trivial es la expresión que evalúa una variable, dado su nombre:

public class VariableExpression : IExpression
{
    private Variable variable;

    public VariableExpression(Variable variable)
    {
        this.variable = variable;
    }

    public Variable Variable { get { return this.variable; } }

    public object Evaluate(Context context, bool withvars = false)
    {
        if (!context.HasValue(this.variable.Name))
            if (!withvars)
                throw new Exception(string.Format("variable '{0}' is unbound", this.variable.Name));
            else
                return this.variable;

        return context.GetValue(this.variable.Name);
    }

    public bool HasVariable()
    {
        return true;
    }
}

Esta vez se usa el contexto. Si la variable no está todavía ligada a un valor, pueden pasar dos cosas: que el Evaluate acepte que alguna variable quede sin ligar, y se devuelve como resultado de la evaluación la variable, o que se dispare una excepción. Vamos a ver que hay ocasiones donde Erlang espera y necesita que una expresión esté completamente evaluada, en el sentido de no contener variables sin ligar.

Les comentaba que todo este código fue implementado siguiendo el flujo de trabajo de TDD. Listo algunos tests abajo para dar constancia. Para expresión constante:

[TestMethod]
public void CreateSimpleConstantExpression()
{
    ConstantExpression expr = new ConstantExpression(10);

    Assert.AreEqual(10, expr.Value);
}

[TestMethod]
public void EvaluateSimpleConstantExpression()
{
    ConstantExpression expr = new ConstantExpression(10);

    Assert.AreEqual(10, expr.Evaluate(null));
}

Para expresión de variable:

[TestMethod]
public void CreateSimpleVariableExpression()
{
    Variable variable = new Variable("X");
    VariableExpression expression = new VariableExpression(variable);

    Assert.AreEqual(variable, expression.Variable);
}

[TestMethod]
public void EvaluateVariableExpression()
{
    Variable variable = new Variable("X");
    Context context = new Context();
    context.SetValue("X", 1);
    VariableExpression expression = new VariableExpression(variable);

    Assert.AreEqual(1, expression.Evaluate(context));
}

[TestMethod]
public void EvaluateUndefinedVariableExpression()
{
    Variable variable = new Variable("X");
    Context context = new Context();
    VariableExpression expression = new VariableExpression(variable);

    Assert.AreEqual(variable, expression.Evaluate(context, true));
}

[TestMethod]
public void RaiseIfEvaluateUndefinedVariableExpression()
{
    Variable variable = new Variable("X");
    Context context = new Context();
    VariableExpression expression = new VariableExpression(variable);

    try
    {
        expression.Evaluate(context, false);
        Assert.Fail();
    }
    catch (Exception ex)
    {
        Assert.AreEqual("variable 'X' is unbound", ex.Message);
    }
}

Próximos posts: explorar otras implementaciones de expresiones, implementaciones de estructuras básicas del lenguaje, como variables y mapas, etc…

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in AjErl, C#, Erlang, Proyectos Open Source | Comments Off on AjErl Implementando Erlang en C# (2) Expresiones

Resoluciones del Nuevo Mes: Mayo 2015

De nuevo, es tiempo de revisión de mis resoluciones del mes pasado, y escribir las del nuevo mes:

– Escribir posts sobre JavaScript e Inteligencia Artificial  [pendiente]
– Mejorar ClojJS [completo] ver repo
– Agregar soporte de módulos npm a ClojJS [pendiente]
– Mejorar el emulador de Chip8 [pendiente]
– Mejorar BScript [completo] ver repo

Además, estuve trabajando en:

– Mejorar AcquarellaJS [completo] ver repo
– Mejorar AjErl, refactor de funciones de tipo [completo] ver repo
– Mejorar OStore, primeros operadores en match a la MongoDB [completo] ver repo
– Mejorar SimpleLists, operación de intersección de listas [completo] ver repo
– Mejorar MeteorSamples, usando Windows version 1.1 [completo] ver repo
– Participar en Google Code Jam, ronda previa [completo] ver repo
– Programar en Proyecto Liqueed [completo] ver repo
– Grabar sesión de programación en equipo del Proyecto Liqueed usando TDD [completo] ver post/video
– Escribir posts sobre el Proyecto Liqueed [complete] leer posts en inglés leer posts en español

Las resoluciones para este nuevo mes de mayo son:

– Mejorar ClojJS
– Agregar soporte de módulos NPM a ClojJS
– Escribir posts sobre JavaScript e Inteligencia Artificial
– Dar una charla sobre Meteor
– Preparar una charla sobre Clojure o ClojureScript
– Mejorar BScript
– Mejorar AjErl, características distribuidas
– Mejorar Proyecto Liqueed, la implementación de kudos

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in .NET, C#, JavaScript, NodeJs, Proyectos Open Source | Leave a comment

Programando con TDD en Proyecto Liqueed

El viernes pasado hubo sesión de programación remota, en Google Hangout, en el Proyecto Liqueed

https://github.com/liquid-co-ops/liqueed

Quedó grabado el uso que le estamos dando a TDD en el proceso de desarrollo:

Se implementó la primera versión de dar ‘kudos”, una forma de premiar a un miembro de un equipo, por envío de kudos de parte de otros miembros, incluso con integración a Slack. Se va a seguir con esta “feature”, y de la misma manera, implementando todo siguiendo el flujo de trabajo de TDD. Pueden ir viendo los commits del proyecto.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in JavaScript, Liqueed, NodeJs, Test-Driven Development, Video | Comments Off on Programando con TDD en Proyecto Liqueed

Proyecto Liqueed (6)

Anterior Post

Sigamos viendo algun test de test/personapi.js en el proyecto:

https://github.com/liquid-co-ops/liqueed

Hay entradas en la API que se llaman por PUT o POST, y reciben datos en el cuerpo del mensaje enviado. En Express, usando el middleware adecuado, ese mensaje es analizado y convertido en un objecto JSON, y puesto en el campo body del objeto request original. Pues bien, si nuestra entrada en la API usa ese campo, se lo tenemos que entregar en el test. Un ejemplo:

exports['login person'] = function (test) {
    test.async();
    
    var request = {
        body: {
            username: persons[1].username,
            password: persons[1].username
        }
    };

    var response = {
        send: function (model) {
            test.ok(model);
            test.equal(model.id, persons[1].id);
            test.equal(model.name, persons[1].name);
            test.equal(model.username, persons[1].username);
            
            test.done();
        }
    };
    
    controller.loginPerson(request, response);
};

Como antes, en el response ponemos la función send, que esperamos se invoque asincrónicamente para dar por ejecutado el test.

Otro ejemplo donde no sólo enviamos body sino también parámetros (esos parámetros en general están en la ruta, y Express los toma de ahí y nos lo deja en request.params:

exports['change password first person'] = function (test) {
    test.async();
    
    var request = {
		params : {
			id : persons[0].id.toString()
		},
        body: {
            password: 'new' + persons[0].username
        }
    };

    async()
    .then(function (data, next) {
        var response = {
            send: function (model) {
                test.ok(model);
                test.strictEqual(model, true);
                
                next(null, null);
            }
        };
        
        controller.updatePassword(request, response);
    })
    .then(function (data, next) {
        var request = {
            body: {
                username: persons[0].username,
                password: 'new' + persons[0].username
            }
        };

        var response = {
            send: function (model) {
                test.ok(model);
                test.equal(model.id, persons[0].id);
                test.equal(model.name, persons[0].name);
                test.equal(model.username, persons[0].username);
                
                test.done();
            }
        };
        
        controller.loginPerson(request, response);
    })
    .run();
};
 

Próximos posts: pruebas del controlador MVC, pruebas levantando el servidor, pruebas usando un DSL (Domain Specific Language)

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Express, JavaScript, Liqueed, NodeJs, Test-Driven Development | Comments Off on Proyecto Liqueed (6)

Proyecto Liqueed (5)

Anterior Post

Examinemos dos tests de test/personapi.js. El segundo test es:

exports['get persons'] = function (test) {
    test.async();
    
    var request = {};
    var response = {
        send: function (model) {
            test.ok(model);
            test.ok(Array.isArray(model));
            test.ok(model.length);
            test.ok(model[0].id);
            test.ok(model[0].name);
            test.done();
        }
    };
    
    controller.list(request, response);
};

Una vez ya armado el modelo de dominio (en el primer test), ahora podemos probar un método del controlador, el getPersons. Como necesita recibir request y response, le pasamos dos objetos armados a propósito, y que tienen lo que el controlador necesita. El response tiene definido un método send, que se espera sea invocado por la acción del controlador. Ahí es donde reside el test de lo retornado, y el test.done() que hace que el test termine exitosamente. Se espera que devuelva un modelo, un objeto JSON que sea un arreglo, que contenga algunos datos.

El tercer test es:

exports['get first person'] = function (test) {
    test.async();
    
    var request = {
        params: {
            id: persons[0].id.toString()
        }
    };

    var response = {
        send: function (model) {
            test.ok(model);
            test.equal(model.id, persons[0].id);
            test.equal(model.name, persons[0].name);
            test.done();
        }
    };
    
    controller.get(request, response);
};

Esta vez se necesita algo en el objeto request, un parámetro adicional indicando la clave primaria de la persona. Sigue habiendo un método send en el response armado para el test, que controla que se devuelvan los datos de la persona pedida.

Vemos que acá no se está invocando HTTP con GET o cosas similares. Estamos probando directamente el código del controlador. De hecho, el código fue escrito luego del tests, y se ejecutó por primera vez desde los tests, sin necesidad de levantar un servidor HTTP.

Próximo post: veremos la prueba de otros métodos de la API, pasándoles objetos, y pruebas del controlador MVC

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Express, JavaScript, Liqueed, NodeJs, Test-Driven Development | Comments Off on Proyecto Liqueed (5)

Proyecto Liqueed (4)

Anterior Post
Siguiente Post

Ya quedó claro en los posts anteriores que gran parte del proyecto:

https://github.com/liquid-co-ops/liqueed

ha sido armado siguiendo el flujo de trabajo de TDD (Test-Driven Development).

En el anterior post vimos un ejemplo de lógica de servicio, cómo quedó implementada siguiendo tests. Veamos hoy que hay controladores, módulos JavaScript que exponen métodos como acciones a mapear por Express, que exponen una API (Application Program Interface) hacia afuera. Por ejemplo, controllers\personapi.js, que comienza declarando:

'use strict';

var service = require('../services/person');

Vean que usa al servicio de personas.

Hay tests correspondientes, en test\personapi.js, que comienza importando los módulos que va a usar:

'use strict';

var controller = require('../controllers/personapi');

var loaddata = require('../utils/loaddata');
var db = require('../utils/db');
var async = require('simpleasync');

En el anterior posts, discutí que en JavaScript la granularidad que prefiero para correr los tests es el módulo, más que una función. Luego ejecuto los tests en orden, dentro de ese módulo. Acá aparece el primer test, que se ocupa de inicializar el modelo de dominio:

var persons;

exports['clear and load data'] = function (test) {
    var personService = require('../services/person');

    test.async();
    
    async()
    .then(function (data, next) { db.clear(next); })
    .then(function (data, next) { loaddata(next); })
    .then(function (data, next) { personService.getPersons(next); })
    .then(function (data, next) {
        persons = data;
        test.ok(persons);
        test.ok(persons.length);
        test.done();
    })
    .run();
};

Lo nuevo aca a entender, es el uso de simpleasync, en la variable async, un módulo que escribí hace un tiempo para encadenar funciones JavaScript. Cada función recibe: data, el resultado de la anterior función, y next, un callback de JavaScript que admite dos argumentos: err y data. La función que definamos en la cadena, puede tomar data como resultado bueno de la anterior función definida en la cadena, y entregar err en null, y el nuevo data para el siguiente eslabón. O puede entregar err, y data en null, si detecta algún problema. Vemos en el ejemplo de arriba, que personService.getPersons(next) invoca la recuperación de la lista de personas, y se le pasa next, como callback. Entonces, la siguiente función en la cadena recibe data como respuesta, y lo utiliza.

A pesar que parece que está usando una base de datos, lo asumido es usar un modelo en memoria. Tenemos que ver dónde está esa decisión, pero hoy nos basta saber que el modelo inicial está en el directorio principal, en formato JSON en testdata.json. Parte de este archivo:

{
    "projects": [
        {
            "name": "FaceHub",
            "periods": [
                { 
                    "name": "January 2014", 
                    "date": "2014-01-31", 
                    "amount": 100,
                    "assignments": [
                        { "from": "Alice", 
                            "to": "Bob", 
                            "amount": 50, 
                            "note": "Arrive earlier" },
                        { "from": "Alice", 
                            "to": "Charlie", 
                            "amount": 50 , 
                            "note": "Arrive earlier" },
                        { "from": "Bob", 
                            "to": "Alice", 
                            "amount": 60 , 
                            "note": "Arrive earlier" },
                        { "from": "Bob", 
                            "to": "Charlie", 
                            "amount": 40 , 
                            "note": "Arrive earlier" },
                        { "from": "Charlie", 
                            "to": "Alice", 
                            "amount": 35 , 
                            "note": "Arrive earlier" },
                        { "from": "Charlie", 
                            "to": "Bob", 
                            "amount": 65 , 
                            "note": "Arrive earlier" }
                    ]
                },
                { "name": "February 2014", 
                    "date": "2014-02-28", 
                    "amount": 100 }
            ],
            "team": [ "Alice", "Bob", "Charlie" ],
//....

Vemos que personapi.js como módulo, al final termina exportando las acciones que queremos consumir:

module.exports = {
    list: list,
    get: get,
    getProjects: getProjects,
    loginPerson: loginPerson,
    getPendingShareProjects:getPendingShareProjects,
    updatePassword: updatePassword
}

Veremos en próximos posts, el segundo y siguientes tests de la API de persona, y cómo las acciones se rutean con Express.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in JavaScript, Liqueed, NodeJs, Test-Driven Development | Comments Off on Proyecto Liqueed (4)