Introducción
Llegamos a la tercera entrega de nuestra introducción al TDD en Javascript. En los capítulos anteriores (I Parte) (II Parte), habíamos visto la teoría general sobre el Desarrollo Dirigido por Tests así como las herramientas más interesantes de las que disponemos para implementarla.
Una vez revisados los frameworks del tipo xUnit, quedan por analizar otras soluciones similares que buscan el mismo resultado pero desde otra perspectiva. De entre estos puntos de vista alternativos, ha ido ganando fuerza la metodología denominada por Dan North como BDD. Conocerla, nos permitirá además enfrentarnos a algunos de los problemas que los expertos han detectado en la TDD más clásica.
BDD (Behaviour Driven Development)
Por lo general, cuando hablamos de TDD (incluyendo esta serie de artículos), no nos referimos a la versión más purista de la metodología sino a su evolución inteligente elaborada a partir de sus críticos. El Desarrollo Dirigido por Tests, como indica bien indica su nombre, se basa en pruebas sobre el valor devuelto por una función, método o cualquier otro tipo de algoritmo de entrada. El hecho de utilizar el término ‘test’ para estas pruebas así como utilizar ‘tests unitarios’, generan bastantes dudas a la hora de aplicarlo: tal y como comenta Dave Astels, realmente quienes se inician en esta metodología no llegan a entender realmente su funcionamiento… ¿Cuál es el problema? Fácil: la terminología.
La metodología TDD exige pensar siempre en tests unitarios (al menos desde su perspectiva más purista). Los métodos son llamados desde un contexto muy específico, con unos argumentos específicos también y comparados finalmente con un valor de salida también muy especifíco:
«El resultado de llamar a la función o método ‘suma’ con los argumentos 2 y 4 da como resultado 6»
Este planteamiento genera fácilmente reacciones negativas tanto para programadores como para sus responsables:
– «No voy a escribir todo ese puñado de tests»
– «Es un código realmente simple, no necesita ser comprobado!»
– «He escrito o utilizado este algoritmo o bucle cientos de veces, no necesito probarlo otra vez»
– «Los tests se escriben después que el código»
– «Tenemos a un equipo dedicado a realizar pruebas sobre el código de los programadores»
– «No podemos invertir tanto tiempo en tests cuando la aplicación ni siquiera hemos comenzado el desarrollo»
Las negativas son muchas, sobre todo cuando contamos nos presionan con los plazos de entrega. Es en este momento cuando necesitamos cambiar el enfoque hacía otros objetivos más prácticos que son de los que hablamos cuando nos referimos actualmente a TDD: no debemos escribir tests, sino especificaciones. Tal y como Bob Martin ha defendido durante años, se trata de «especificaciones, no verificaciones». Esto quiere decir que las verificaciones (los tests) son para comprobar que un código funciona correctamente mientras que las especificaciones, son para definir el significado de que nuestro código funcione. Y aquí encontramos la base del BDD que habitualmente fusionamos con el TDD entendiéndolo como una evolución: un desarrollo dirigido por comportamiento en lugar de por tests.
El BDD no es finalmente más que un vocabulario diferente a la hora de afrontar nuestros desarrollos: en lugar de hacer un test unitario, realizamos un análisis del contexto. En lugar de escribir métodos que comiencen con la palabra ‘test’, los comenzamos con un ‘debería’ (should). En este sentido, los frameworks basados en BDD ofrecen una sintaxis ligeramente modificada a los que vimos en TDD incorporando este vocabulario más enfocado a una especificación que a una verificación.
Para más información sobre BDD, podemos revisar el artículo original de su creador Introducing BDD.
Catálogo de frameworks con estilo BDD
Pese a que muchos frameworks TDD puros han ido modificando su vocabulario para acercarse al BDD (proyecto jBehave por ejemplo), existen varios de ellos a los que podemos considerar ‘puros’. Actualmente, los principales son:
Jspec: http://visionmedia.github.com/jspec
JsSpec: http://jania.pe.kr/aw/moin.cgi/JSSpec
Jasmine: http://github.com/pivotal/jasmine
Screw.Unit: http://github.com/nkallen/screw-unit
Inspec: http://github.com/aq1018/inspec
jShoulda: http://jshoulda.scriptia.net
SugarTest: http://sugartest.scriptia.net
Como en el artículo anterior, revisemos los más interesantes con mayor detalle.
JSpec
Como la mayoría de los frameworks de tipo BDD, la estructura y sintaxis de JSpec está fuertemente influenciado por el RSpec de Ruby. En JSpec, como curiosidad tenemos el que los ‘tests’ (continuaremos utilizando el término para facilitar la lectura) pueden escribirse, en lugar de en Javascript puro, en un lenguaje interpretado propio con muchas similitudes a Ruby. Alternativamente, también se nos permite usar Javascript plano.
Ejemplo de Sintaxis
describe 'ShoppingCart' before_each cart = new ShoppingCart end describe 'addProducts' it 'should add several products' cart.addProduct('cookie') cart.addProduct('icecream') cart.should.have 2, 'products' end end describe 'checkout' it 'should throw an error when checking out with no products' -{ cart.clear().checkout() }.should.throw_error EmptyCart end end end |
Mismo ejemplo en Javascript plano
JSpec.describe('ShoppingCart', function(){ before_each(function{ cart = new ShoppingCart }) describe('addProducts', function(){ it ('should add several products', function(){ cart.addProducts('cookie') cart.addProducts('icecream') expect(cart).to(have, 2, 'products') }) }) describe('checkout', function(){ it ('should throw an error when checking out with no products', function(){ expect(function(){ cart.clear().checkout() }).to(throw_error, EmptyCart) }) }) }) |
Ventajas
- Sintaxis similar a Ruby.
- Entorno de Mocks integrado.
- Robusto conjunto para seleccionar elementos, incluyendo selectores jQuery.
- Se puede extender con módulos.
- Permite evaluar código asíncrono.
- Integración con Ruby on Rails.
Desventajas
- Sintaxis similar a Ruby.
- Sintaxis especial para implementar helpers.
Conclusión
Sin duda, se trata de una plataforma muy sólida. Si estamos familiarizados con Ruby, esta puede ser nuestra opción más interesante.
JSSpec
Uno de los más veteranos en cuanto a estilo BDD, fue el utilizado para algunos proyectos punteros como MooTools.
Ejemplo de Sintaxis
describe('"Should match"s', { 'Should match': function() { expect( "Hello" ).should_match( /ell/ ); }, 'Should match 1': function() { expect( "Hello" ).should_match( /x/ ); }, 'Should match 2': function() { expect( [ 1, 2, 3 ] ).should_match( /x/ ); }, 'Should not match 1': function() { expect( "Hello" ).should_not_match( /ell/ ); }, 'Should not match 2': function() { expect( [ 1, 2, 3 ] ).should_not_match( /x/ ); } }) |
Ventajas
- Buena y clara salida de los resultados en HTML.
- Permite seleccionar mediante condicionales en qué navegador se ejecutarán los tests.
Desventajas
- Sintaxis algo caótica.
- Proyecto descontinuado.
Conclusión
Pese a que se trata de un framework sólido, sus funcionalidades son limitadas frente a los competidores más modernos. Personalmente lo encuentro un par de peldaños por debajo de Jasmine.
Jasmine
Jasmine es otro framework con una arquitectura similar a JSpec. Creado por por los autores de jsUnit, es a día de hoy la solución más adoptada por los desarrolladores tanto de la comunidad Ruby como aquellos provenientes de otros lenguajes.
Ejemplo de Sintaxis
describe("Player", function() { var player; var song; beforeEach(function() { player = new Player(); song = new Song(); }); it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); //demonstrates use of custom matcher expect(player).toBePlaying(song); }); }); |
Ventajas
- Fácil de comenzar a utilizar. Sólo hay que replicar el ejemplo.
- Sintaxis clara.
- La respuesta HTML es elegante y sencilla.
- Soporte para código asíncrono.
Desventajas
- Nada que destacar.
Conclusión
Posiblemente el mejor framework si los ponemos a todos en una balanza. Es mi opción personal y la de muchos de mis colegas. En las próximas entregas, cuando comencemos con los ejemplos, será la herramienta seleccionada para realizar nuestras pruebas.
Otros tipos de frameworks
Además de aquellos paquetes dirigidos especialmente a las metodologías TDD y BDD, tenemos a nuestra disposición otras herramientas interesantes que o bien complementan las anteriores o bien sirven para testear nuestras aplicaciones desde otros ángulos. De entre aquellas del primer grupo, destacan los frameworks para crear mocks u objetos simulados.
Mocks
En la programación orientada a objetos, un mock es un objeto simulado o de sustitución encargado de imitar el comportamiento de un objeto real dentro de un contexto controlado. Una de las aplicaciones más comunes para este tipo de objetos es la de simular la respuesta de otro objeto como, por ejemplo, una petición AJAX. Su uso es muy controvertido dentro de las metodologías de desarrollo debido a la debilidad que presentan: si modificamos el objeto original, debemos recordar modificar el objeto simulado para no crear inconsistencias.
En Javascript, prácticamente el uso de estos objetos se limitan a emular la respuesta de un servidor frente a peticiones asíncronas, resultando útiles cuando no se dispone de una plataforma REST implementada.
Existen varios frameworks de este tipo actualmente a disposición de los desarrolladores. Algunos de los más reconocidos y completos son:
JsMockito: http://jsmockito.org
Mockjax: https://github.com/appendto/jquery-mockjax
jqMock: http://code.google.com/p/jqmock
JSMock: http://jsmock.sourceforge.net
amok: http://code.google.com/p/amok
Jack: http://boss.bekk.no/display/BOSS/Jack
JJ: http://github.com/nakajima/jj
De la lista anterior, analizaremos únicamente los dos primeros por resultar las opciones más interesantes.
JsMockito
Inspirado en Mockito, este framework ofrece una API simple y clara, sin necesidad de recurrir a más dependencias.
Ejemplo de Sintaxis
mockFunc = mockFunction(); when(mockFunc)(anything()).then(function(arg) { return "foo " + arg; }); // -- start code under test -- mockFunc("bar"); // -- end code under test -- verify(mockFunc)(anything()); // or if you want to verify the scope it was called with, use: verify(mockFunc).call(this, anything()) |
Ventajas
- Fácil integración con los frameworks tipo xUnit o BDD.
- Capaz de replicar funciones y métodos.
Desventajas
- Nada que destacar. Realiza su trabajo como se espera.
Conclusión
Si nos decidimos a utilizar un framework de este tipo, sin duda puede ser la mejor opción. Poco más hay que decir cuando las cosas se hacen bien.
Mockjax
Comentado ya en un artículo reciente, Testeando AJAX en jQuery, Mockjax es un framework desarrollado como un plugin de jQuery el cual permite emular a la perfección el comportamiento de un servidor.
Ejemplo de Sintaxis
$.mockjax({ url: '/foo/bar.html', response : function( settings ){ var data = settings.data; this.responseText = data; } }); |
Ventajas
- Perfectamente integrado con jQuery.
- Intercepta todas las peticiones AJAX para emular una respuesta por parte del servidor.
- Amplio conjunto de opciones configurables para el servidor emulado.
- Sintaxis elegante al estilo jQuery.
Desventajas
- Requiere de jQuery.
- Monitoriza únicamente las llamadas AJAX.
Conclusión
Si trabajamos con jQuery y necesitamos controlar las respuestas de nuestras peticiones a servidor, este plugin es sin duda alguna la opción perfecta. La gran cantidad de opciones configurables permite simular todos los supuestos que pueden darse durante la ejecución asíncrona de nuestro código ahorrándonos la tediosa tarea de requerir una arquitectura REST implementada.
Conclusión
Y hasta aquí la tercera entrega de nuestro curso sobre TDD en Javascript. En esta ocasión, hemos repasado algunas de las herramientas más interesante con las que contamos los desarrolladores para comenzar a utilizar esta metodología. En próximas entregas, pondremos en práctica toda la teoría vista hasta ahora con ejemplos.
Para mock, he probado ultimamente este y no está nada mal:
http://sinonjs.org/
Me llamó la atención inicialmente por los ejemplos para crear un fake object del XMLHttpRequest.
Enhorabuena por la serie de artículos 🙂
Salut,
Ricardo
Hola Ricardo;
Sinon.js es una gran librería para practicar TDD. Por ejemplo, me gusta mucho el reloj interno que tiene y que permite lanzar timeouts síncronos. Esa utilidad es perfecta para emular a un usuario escribiendo en un formulario incluyendo las pausas entre cada pulsación de tecla.
En próximos artículos tengo pensado utilizar esta librería para explicar el conceptro de tests ‘top-down’.
Un saludo!