Ya saben que soy un gran defensor de TDD (Test-Driven Development), que es un flujo de trabajo para el desarrollo de software, donde lo que vamos construyendo es a partir de escribir tests que describan lo que queremos, e implementar de la forma más simple lo que se pide en el tests. En el proyecto Liqueed se adoptó, para gran parte del código, esta forma de trabajo.
Por ejemplo, hay un archivo services\person.js, que es un módulo JavaScript, que maneja la lógica de las personas. Una persona puede participar en cero, uno o varios proyectos, tener puntos acumulados, votar, etc. Y el sistema tiene que mantener la lista de las personas, ubicarlas por id, por código de usuario, etc. Parte de esa lógica está en este módulo de servicio. Al armarlo, se fue escribiendo un test\person.js con los tests que describen la lógica a soportar. Al comienzo de ese archivo, se declaran:
'use strict'; var service = require('../services/person'); var pservice = require('../services/project'); var async = require('simpleasync');
Se usa el servicio person.js, y en algún test se usa también alguna lógica del servicio de proyectos. simpleasync es un módulo sencillo que escribí para encadenar la llamada de callbacks. Me resultó muy interesante escribirlo y me sirvió en varios proyectos.
Luego, se declaran dos variables de módulo, auxiliares, para reusar en varios tests. Son los ids de algunas personas de prueba a crear y usar:
var annaid; var lauraid;
Para ejecutar los tests estoy usando simpleunit, otro módulo que escribí, de nuevo para practicar JavaScript, TDD y simplicidad. Está inspirado en nodeunit, pero es más simple. simpleunit ejecuta las funciones exportadas de un módulo, pasándole como argumento un objeto test, similar al assert de NodeJS.
El primer test de este archivo test\person.js ejercita el agregar una persona:
exports['add person'] = function (test) { test.async(); service.addPerson({ name: 'Anna' }, function (err, result) { test.ok(!err); test.ok(result); annaid = result; test.done(); }); };
Como es un callback cuya ejecución es asincrónica (con lo que la función exportada puede terminar ANTES que se cumpla la creación de la persona), hay que declarar test.async() para indicarle a simpleunit que tiene que esperar a que el test se corra completamente, y test.done() para indicar que se terminó la ejecución. En este test sólo se prueba que se devuelva un id de la nueva persona, y se lo guarda en una variable de módulo. Una cosa que me resultó fácil, pero que no se recomienda en otros casos (como C#), es el tener una secuencia de tests, es decir, que cada test pueda depender de haber ejecutado otros. Hasta ahora, en JavaScript, el nivel de aislamiento/corrida es el módulo de test, no la función de test. Pero veremos si alguna vez esto expone limitaciones.
Veamos un test más:
exports['get person by id'] = function (test) { test.async(); service.getPersonById(annaid, function (err, result) { test.ok(!err); test.ok(result); test.equal(result.name, 'Anna'); test.equal(result.id, annaid); test.equal(result.username, 'anna'); test.done(); }); };
Ahora, con el id generado en el anterior test, recuperamos a la persona, y comprobamos sus datos.
Vean que la serie de tests no presupone nada sobre el estado del dominio: puede haber o no otras personas, proyectos, etc. Vamos a ver también, que justo estos tests están usando un dominio en memoria, pero también hay tests que usan una base de datos de tests. Pero lo interesante del proyecto, usando TDD, es que al principio todo el dominio estaba en memoria, y solamente ya avanzado algún caso de uso, se pasó a implementar persistencia. Pueden ver la historia de commits en el repositorio para ver cómo el sistema fue creciendo, como un organismo, agregándole cosas poco a poco, sin pensar por adelantado, cambiando y refactorizando código de manera sencilla, y con confianza, al tener una batería de tests de mini-casos de uso.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez