QUnit es una completa suite para testear nuestras aplicaciones Javascript desarrollada por el jQuery Team. En principio fue concebida para usar sobre el propio código jQuery pero su flexibilidad permite integrarla fácilmente en cualquier desarrollo tanto en cliente como en su versión servidor (Rhino, V8, node.js).
QUnit utiliza para comprobar el funcionamiento del código la metodología conocida como Prueba Unitaria.
Concepto de prueba unitaria
En programación, una prueba unitaria es una forma de probar el correcto funcionamiento de un módulo de código. Esto sirve para asegurar que cada una de las partes que integran nuestra aplicación funcionen correctamente por separado.
Las ventajas de las pruebas unitarias frente otras formas de test son el que pueden aplicarse de una forma automatizada con antelación a la puesta en producción de un código. Esto quiere decir que no es necesario el reproducir de forma manual todo el flujo necesario que lleva a la obtención del dato a verificar sino que en su lugar, delegamos esa tarea a nuestro framework de pruebas. Una ventaja añadida para los desarrolladores web es que pueden reproducir las baterías de test en cada uno de los diferentes navegadores de una forma casi desatendida. En caso de encontrarse un problema, como las pruebas se ejecutan aisladas, llegar al error resulta más sencillo. Una vez reparado, se vuelven a ejecutar los tests para comprobar la integridad en el resto de la aplicación.
En cuanto a la calidad de nuestro código, Javascript es un lenguaje muy apto para la refactorización. Al tratarse de un contenido que tiene que ser previamente descargado en cliente y ejecutado sobre una amplia variedad de plataformas, es imprescindible alcanzar el mayor rendimiento con el menor número de líneas posible. Para esta etapa en el desarrollo, contar con una batería consistente de test puede resultar de gran ayuda. Tras cada pequeña modificación del código durante la refactorización, es muy recomendable lanzar los tests para comprobar que se mantienen las funcionalidades esperadas.
El magnífico libro de Carlos Blé Jurado, Diseño Ágil con TDD, liberado bajo licencia Creative Commons y descargable desde su página oficial, explica en su capítulo IV el concepto de test unitario a través de sus características:
- Atómico. Significa que el test prueba la mímima cantidad de funcionalidad posible.
- Independiente. Significa que el test no depende de otros para producir un resultado: funciona con independencia de otros tests.
- Inocuo. Un test nunca altera el estado del sistema. Su ejecución, por ejemplo, no creará nuevos ficheros o registros en una base de datos.
- Rápido. Debe realizarse lo más rápido posible para no interrumpir una dinámica de trabajo. Para el caso de Javascript y las llamadas Ajax, esta característica no siempre puede alcanzarse debido a latencias y tiempos de respuesta del servidor.
Todos los puntos anteriores conforman el acrónimo inglés F.I.R.S.T con el que también pueden definirse las pruebas unitarias: Fast, Independent, Repeatable, Small y Transparent.
Integrando QUnit en Javascript
Para comenzar a utilizar QUnit, solo necesitamos incluir en nuestros ficheros dos archivos: un script con la librería y una hoja de estilos (opcional) que permite una correcta visualización de los resultados.
<link rel="stylesheet" type="text/css" href="http://github.com/jquery/QUnit/raw/master/QUnit/QUnit.css" media="all" /> <script type="text/javascript" src="http://github.com/jquery/QUnit/raw/master/QUnit/QUnit.js"></script> |
Como puede verse por las URLs, podemos elegir no guardar la libreria en nuestro servidor y utilizar la última versión hospedada en el repositorio público de github.
Si optamos por almacenar nuestra propia copia local, podemos descargar ambos archivos desde la página oficial de QUnit.
Además de estos archivos, nuestras aplicaciones tienen que contar con una serie de etiquetas HTML donde se mostrarán los resultados de nuestros tests:
<h1 id="QUnit-header">QUnit Test Suite</h1> <h2 id="QUnit-banner"></h2> <div id="QUnit-testrunner-toolbar"></div> <h2 id="QUnit-userAgent"></h2> <ol id="QUnit-tests"></ol> |
Primeras pruebas
Cada uno de los bloques que componen nuestros test se llaman «afirmaciones» (assertions). Una afirmación puede definirse como un estamento que evalúa el resultado de una determinada porción de un código. Si el resultado obtenido es correcto (true), el test se supera; si obtenemos un valor falso (false), el test no se supera.
Un test se compone de tres partes básicas que se identifican mediante las siglas anglosajonas AAA : Arrange (Preparar), Act (Actuar) y Assert (Afirmar).
La preparación es imprescindible antes de comenzar a evaluar afirmaciones y su cometido es iniciar el contexto o marco de pruebas.
En QUnit, utilizamos para ello la función test() cuya sintaxis es la siguiente:
test( name, [expected], test ) |
Donde:
- name (String) Nombre del test.
- expected [opcional] (Number) Número de afirmaciones que se esperan ejecutar. Lo veremos más adelante; de momento no lo usamos.
- test (Function) Conjunto de afirmaciones que van a evaluarse. Al menos, debemos contar con una.
Una vez realizado el SetUp, pasamos al acto donde llamaremos al código que queremos probar y que la terminología denomina SUT, Subject/System Under Test (objeto bajo test).
Finalmente, pasamos a la afirmación, donde comprobamos que el SUT (el objeto que estamos comprobando) funciona como se espera.
En QUnit, la afirmación más básica la proporciona la función ok(), cuya sintaxis es la siguiente:
ok( state, message ) |
Donde:
- state (Boolean) Expresión booleana o de cualquier otro tipo que será evaluada.
- message [opcional] (String) Mensaje de salida que acompaña el resultado de la afirmación.
Es una afirmación booleana, equivalente a assertTrue en JUnit. Si el argumento obtenido es un valor true, el test se supera.
Tomemos por ejemplo, la siguiente función:
function checkNumber( my_string ){ return ( parseFloat( my_string ) == my_string ? true : false ); } |
La idea es comprobar si el argumento que pasamos a la función se corresponde con un número. Si se cumple la condición, la función devuelve true, para el caso contrario, obtendríamos false.
Integremos ahora una serie de pruebas unitarias básicas:
test( 'Testing checkNumber()', function() { ok( checkNumber( 0 ) ); ok( checkNumber( 2 ) ); ok( checkNumber( -4 ) ); ok( checkNumber( 1 ) ); ok( checkNumber( 'asdf' ) ); ok( !checkNumber( 'asdf' ) ); } ); |
Para este caso, hemos escrito 6 afirmaciones de tipo booleano en el que comprobamos el valor obtenido con cada llamada a nuestra función (SUT).
Cuando cargamos nuestro archivo en el navegador, obtenemos lo siguiente:
La afirmación ok() equivale internamente a preguntar simplemente si el SUT es true o false, por lo que podría desarrollarse de la siguiente forma con idénticos resultados:
test( 'Testing checkNumber() with true', function() { ok( checkNumber( 0 ) == true ); ok( checkNumber( 2 ) == true ); ok( checkNumber( -4 ) == true ); ok( checkNumber( 1 ) == true ); ok( checkNumber( 'asdf' ) == true ); ok( !checkNumber( 'asdf' ) == true ); } ); |
Afirmaciones más flexibles
Las anteriores afirmaciones solo evalúan un valor booleano, pero no es difícil imaginar otros escenarios en los que se precisa mayor flexibilidad. Pensemos en aquellos casos donde una función devuelve un objeto, un literal u otra función. Para estos casos, contamos con distintos tipos de afirmaciones de igualdad.
Tomemos ahora por ejemplo la siguiente función:
function areaTriangle( tBase, tHeight ){ return ( tBase * tHeight ) / 2; } |
En este caso, no obtenemos un valor booleano sino numérico y por lo tanto, la función ok() no sería la más adecuada. En su lugar, para verificar que el resultado obtenido es el esperado, recurrimos a la función equal(), cuya sintaxis es la siguiente:
equal( actual, expected, [message] ) |
Donde:
- actual (Object) El objeto a evaluar.
- expected (Object) El resultado experado.
- message [opcional] (String) Mensaje que acompaña al resultado de la afirmación.
Básicamente, el funcionamiento de esta afirmación es el siguiente: si actual == expected el test se resuelve correctamente.
test( 'Testing checkNumber()', function() { equal( areaTriangle( 4, 3 ), 6 ); equal( areaTriangle( null, 3 ), 6 ); } ); |
El resultado obtenido con esta batería es el siguiente:
Una forma de mantener todos nuestros tests organizados es definiendo módulos que más tarde podemos ejecutar de forma unitaria. Para definir módulos en QUnit utilizamos la función module():
module( name, [lifecycle] ) |
Donde:
- name (String) es el nombre del módulo.
- lifecycle [opcional] (Options) Permite establecer callbacks que se ejecutarán tanto antes como después de cada test que compone este módulo.
De este modo, podríamos reunir los dos test anteriores en dos módulos independientes:
module( "Test Module A" ); test( 'Testing checkNumber()', function() { ok( checkNumber( 0 ) ); ok( checkNumber( 2 ) ); ok( checkNumber( -4 ) ); ok( checkNumber( 1 ) ); ok( checkNumber( 'asdf' ) ); ok( !checkNumber( 'asdf' )); } ); module( "Testing areaTriangle()" ); test( 'Testing checkNumber()', function() { equal( areaTriangle( 4, 3 ), 6 ); equal( areaTriangle( null, 3 ), 6 ); } ); |
Afirmaciones Estrictas
Además de las vistas anteriormente, QUnit permite otra serie de afirmaciones como strictEqual o deepEqual.
strictEqual realiza una comprobación estricta tanto de tipos de datos como de valores. Su uso es similar a equal:
test('Comparing strict and no-strict modes', function() { equals( 0, false, 'true'); same( 0, false, 'false'); equals( null, undefined, 'true'); same( null, undefined, 'false'); }) |
El resultado es similar al de las comparaciones básicas en Javascript:
deepEqual realiza una comparación recursiva perfecta para su uso en objetos y arrays.
var o1 = { 'foo' : 'First Property', 'bar' : 'Second Property' }; var o2 = { 'foo' : 'First Property', 'bar' : 'Second Property' }; module( "Testing Recursive" ); test( 'Testing Recursive()', function() { equal( o1, o2 ); strictEqual( o1, o2 ); deepEqual( o1, o2 ); } ); |
Ampliando funcionalidades de QUnit
Recientemente han aparecido algunos interesantes proyectos que pueden complementar las funcionalidades de QUnit como puede ser Sinon.js.
Sinon.js es un framework completo para realizar bancos de test que posée un plugin (sinon-QUnit) para integrarlo con QUnit.
Esta herramienta permite recoger los argumentos y los valores devueltos por aquellas funciones que comprobamos para reutilizarlos más adelante. Toda la documentación al respecto podemos encontrarla en su página oficial.
Conclusión
Contar con un banco de pruebas consistente es una garantía de que nuestras aplicaciones funcionarán correctamente antes de su puesta en un entorno de producción. Las ventajas de esta metodología frente a los métodos tradicionales de ensayo y error son la capacidad de reproducir de forma desatendida el comportamiento de un usuario frente a la aplicación final. En el desarrollo web, esta flexibilidad es especialmente interesante cuando queremos comprobar la respuesta del servidor ante un formulario enviado por el usuario, una combinación de eventos tediosa de repetir o una arquitectura Javascript compleja.
Prácticamente todos los lenguajes de programación modernos cuentan con frameworks especializados en realizar este trabajo y Javascript, dada sus características y su dificultad natural para rastrear errores, no puede prescindir de uno. QUnit, desarrollado por el equipo de jQuery es una fantástica opción para delegar este trabajo: su flexibilidad, basada en la misma librería jQuery, facilita la integración de plataformas de terceros que pueden aportar nuevas funcionalidades.
En este artículo, hemos repasado parte de la teoría que hay detrás de las pruebas unitarias y las hemos aplicado al framework estudiado. Con estas herramientas, estamos en disposición de crear nuestros propios tests y comenzar a disfrutar de los beneficios de esta metodología íntimamente ligada con el desarrollo ágil de software.
Más Información
– Carlos Blé Jurado, Diseño Ágil con TDD (libro electrónico con licencia Creative Commons 3.0 )
– Página Oficial de QUnit
– Proyecto sinon.js (completo framework independiente para tests integrable en QUnit)
¡Buen artículo!
Un trabajo fantástico, aunque creo que para completar el artículo pondría una par de frases definiendo que es TDD, y algún enlace de referencia. Ya que te adentras en el mundo de las pruebas unitarias, las cuales son el núcleo de TDD.
Pero vamos, tan sólo se trata de una recomendación.
¡Saludos!
Gracias Xeka; en breve tengo previsto publicar un artículo completo sobre TDD pero antes, tengo que trabajarlo un poco más.
Saludos!
Entonces esperaré con ansia ese artículo que me anuncias.
¡Saludos!
El mejor blog de javascript en español! siento que acá me quito lo de novato. Gracias.
Gracias Carlos. Gracias a ti he aprendido a manejar un poco las pruebas unitarias con QUnit. La verdad es que me lohas hecho muchísmo más fácil que en otros blogs que he visto por ahí incluso en inglés. Y no es un problema de idioma.
Me temo que me acabo de enganchar a tu blog, a ver sí consigo poco a poco ir mejorando como front coder.
Muchas gracias.