TDD y Diseño de Implementación (2)

Post Anterior

Vuelvo a uno de mis temas preferidos: Test-Driven Development, TDD. Veamos hoy cómo TDD promueve lo simple y el desarrollo evolutivo de nuestro software de producción, y voy referencias ejemplos propios en JavaScript/Node.js y en C#.

Recordemos el ciclo:

- Escribir el test

- Correrlo en rojo

- Escribir el mínimo código para pasar a verde

- Refactorizar

- Y así repetir con otro test

Hay quienes escriben varios tests. Yo prefiero ir poniendo un test a la vez. Cuando trabajo en un proyecto de código abierto, dejo en mi cuenta de GitHub los commits POR TEST, así pueden ver que cumplo con “put the money where your mouth is”. Como ejemplo temprano, pueden ver lo que estoy implementando en:

https://github.com/ajlopez/SharpMongo
https://github.com/ajlopez/SharpMongo/commits/master

una especie de MongoDB en memoria en C#. O pueden seguir los videos de TDD Rocks!:

http://msmvps.com/blogs/lopez/archive/2013/07/27/tdd-rocks-4-sharpbase-in-c.aspx

sobre un proyecto de ejemplo:

https://github.com/ajlopez/SharpBase
https://github.com/ajlopez/SharpBase/commits/master

Al escribir el mínimo test que pase a verde, pasan dos cosas:

- A veces, hacemos “cheating”. Cuando un método nuevo tiene que retornar null, simplemente ponemos return null en la primera implementación.

- Eso nos obliga a poner más tests, para obligarnos a que el método realmente se enfrente a otras situaciones y tengamos que comenzar a codificar lo que queremos.

- En algún momento, ponemos un algoritmo más cobertor de los casos de uso (los tests son ejemplos de cómo consumir lo que estamos armando, si quieren verlo así).

Pero no hago gran diseño de antemano. Pienso algunas cosas, por ejemplo, estoy escribiendo un intérprete Prolog en JavaScript/Node.js:

https://github.com/ajlopez/SimpleProlog

y entonces pienso: “necesito manejar átomos, variables, estructuras, tener una base de hechos, resolver variables con unificación… “. Pero si ven los tests, ellos son los que me guían a ir implementando nuevas características, y práctimente cada línea de código de producción puede ser traceada (su nacimiento) hasta la aparición de un test que me obligó a codificarla (si tienen paciencia pueden seguir la evolución de binding.current() y binding.reset() cómo fue cambiando según se presentaron casos de uso más complicados).

Por supuesto, Uds. pueden pensar el diseño más profundamente. Yo hasta ahora, en mis proyectos personales y en privados de una sola persona (yo ;-) no he tenido que hacerlo. Puedo pensar un poco el diseño en el desayuno, y luego, el resto del día dedicarme a implementar lo nuevo que quiero que tenga el proyecto. Pero si piensan el diseño en detalle: por favor, que eso no influya en el código que escriban hoy. Lo único que tiene que influir son los test de HOY, no lo que tengan en la cabeza para mañana.

Todo este flujo de trabajo nos lleva a que nuestro software sea lo más simple posible, dado los casos de uso. Trato de no agregar código que no venga impuesto por un caso de uso del software. Si alguien dice: “Ah! Pero si tal parámetro llega en nulo, hay que controlarlo y dar tal excepción”. Entonces voy, ESCRIBO EL TEST pasando el parámetro en nulo, da rojo, y luego voy y escribo el código de control del parámetro.

Alguien podría preguntar: ¿pero si te equivocás en el diseño? o ¿si viene un requerimiento que cambia la manera de hacer las cosas internamente? Bueno, TDD me da el coraje de hacer refactorizaciones quirúrgicas (así las llamo yo). Pero cuando estoy implementando el requerimiento 4, no estoy pensando “Ah! necesito tal estructura porque la voy a necesitar en el requerimiento 10”. PARA NADA. Cuando llegue a ese requerimiento, recién ahí voy a poner eso.

Por ejemplo, en el MongoDB en memoria con C# o con JavaScript, que estoy escribiendo, ES CLARO que lo que en MongoDB real es:

db.customers.find()

devuelve un cursor, bien “affiatado” a poder recorrer una colección que puede ir cambiando, que me devuelve sólo los primeros documentos y que al ir avanzando él solito se ocupa de ir recuperando lo que sigue. ¿Tengo que pensar todo eso ahora e implementarlo?

NO. De nuevo: NO. Ahora, en la implementación de JavaScript al día de hoy, sólo tengo que devuelve un arreglo. Y en la de C#, tengo que devuelve un IEnumerable de documentos, que es más como el cursor de MongoDB, pero es frágil a que otro thread vaya modificando la lista original. ¿Codifico algo ahora? NO ¿Lo tengo en el radar? Si, cuando llegue el requerimiento, escribiré los tests y haré una mejor implementación interna.

A ver, repitan conmigo la tercera ley aj de software:

No cruzar el puente antes de llegar al puente

TDD me permite cumplir fácil con eso. El cambio que implica implementar ese requerimiento: “find no me da todo de golpe, me lo da a medida que lo voy pidiendo, soporta concurrencia de actualizaciones, etc….” puede ser grande o mediano, pero TDD me da el CORAJE para ir implementándolo. Tal vez no todo de golpe. Recuerdo a Kent Beck:

Make it works, make it right, make it fast

Esta semana pasada me tocó uno de esos refactor quirúrgicos. Pues bien, el martes tenía “make it works” por la mitad, pero algunos casos de uso andaban. El miércoles tenía “make it right”, todos los casos de uso de lo nuevo andando, pero con una implementación no muy bueno. El jueves pasó a “make it right”: refactor interno para que la implementación quedara en mejor forma. Y todo eso dedicándole menos de 3 horas cada día.

En otros casos de refactorización adopto “baby steps”: ir cambiando la implementación de a poco. En vez de pasar todo a base de datos, paso una sola entidad. En vez de pasar a persistencia con base de datos, paso a una simple NoSQL, para ir avanzando sin tener un esquema duro.

Todo eso contribuye para que el sistema vaya creciendo orgánicamente, como un organismo: de a poco, y no de a saltos.

Más arriba puse como ejemplo proyectos privados con un equipo de uno: yo mismo. Lo que me cuesta convencer a un equipo de 3 a 5 personas de seguir lo de arriba. Hay que tener disciplina para aplicar TDD a todo código que escribamos. Si no se hace así, van a ver que entonces TDD a medias tiene problemas. El problema es el refactor, y el refactor quirúrgico: lo que no hicieron con TDD les va doler cambiarlo. Van a tener mucho re-trabajo. Si tienen que seguir las recomendaciones de arriba, van a tener que implementar TDD en la mayoría del código.

Una última cosa: TDD no es escribir test unitarios. A mí, a esta altura, no me importa sin son test unitarios, federales, o de color azul como un pitufo. Lo importante es escribir el software de a poco, viendo el test nuevo como un ejemplo de uso, como un desafío de un video juego que tenemos que resolver, pasando de rojo a verde. En futuro post comentaré qué es un test unitario, sobre lo que creo que hay confusión.

Nos leemos!

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

This entry was posted in 10549, 11699, 12081, 1389, 14005, 15550, 5374. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>