AngularJS: Tests Unitarios y de Extremo a Extremo

Logo de AngularJS

Este es el cuarto post de una serie en la que estoy explicando las características más destacadas de angularjs. Hasta ahora hemos visto los conceptos básicos, la forma en que se trabaja con servicios, inyección de dependencias y módulos, y cómo mejorar la capa de presentación usando directivas y filtros.

Hoy quiero analizar algo que, por desgracia, para muchos será irrelevante, pero que para mi es fundamental antes de decidirme a utilizar un framework: el soporte para testearlo.

En este blog que hablado mucho sobre tests automatizados (ya fuera aplicando TDD o no) y he insistido en la importancia de los tests en javascript.

Si ya considero que los tests automatizados son una de las cosas que más calidad aporta a un proyecto, en el caso de un lenguaje dinámico como javascript creo que su importancia aumenta enormemente al perder al seguridad que ofrece la comprobación de tipos que realiza el compilador.

En ese sentido, angular está diseñado desde el principio para poder ser fácilmente testeable y para ello cuenta con dos tipos de tests diferenciados, tests unitarios y tests de extremo a extremo.

OJO: en estos post no vas a encontrar información detallada sobre cómo trabajar con angular ni mucho código fuente, si estás buscando ese tipo de detalles, te recomiendo que eches un vistazo a la documentación oficial o sigas el tutorial. Si quieres profundizar en el tema, AngularJS: Up and Running me pareció un buen libro y tengo buenas referencias del curso de AngularJS que ofrecen Pedro Hurtado y Xavier Cerdá.

Karma

Los sistemas de tests empleados por defecto en angular, tanto unitarios como extremo a extremo, se basan en el uso de Karma, un lanzador de tests que se integra con distintos frameworks (Jasmine, QUnit, Mocha, etc.) para ejecutar tests en navegadores reales (Firefox, Chrome, Phantom, e incluso Internet Explorer).

Esto nos permite comprobar que la aplicación funciona realmente sobre los motores de javascript que la van a ejecutar, lo que nos ayudará a detectar posibles problemas por incompatibilidades de javascript entre ellos.

La configuración de Karma no es todo lo sencilla que me gustaría y su integración con herramientas como grunt a veces resulta un poco complicada, sobre todo por la gestión de versiones (algo demasiado frecuente en el cambiante mundo de las librerías javascript). Un buen punto de partida para configurarlo es el proyecto angular-seed, que ofrece una plantilla de proyecto ya configurada.

Aun así, una vez que lo tienes configuarado es bastante estable y se puede usar sin demasiados problemas con un servidor de integración continua.

Tests Unitarios

Con angular podemos escribir tests unitarios que nos permitan definir y comprobar el funcionamiento de los componentes de la aplicación por separado.

Aprovechando el sistema de inyección de dependencias es sencillo modificar las dependencias de los componentes para inyectar mocks/stubs/fakes/lo-que-más-te-guste y poder aislar el componente que estamos testeando.

No soy muy partidario de los tests de interacción con mocks, y en el caso de javascript todavía menos, pero a veces no queda más remedio que utilizarlos para testar cosas concretas.

Aun así, siguiendo los mismos principios para escribir tests unitarios que aplicaríamos en otros lenguajes es posible conseguir una buena suite de tests en la que podamos confiar.

Tests de Extremo a Extremo

Usando Karma podemos también ejecutar tests de integración de extremo a extremo. Estos tests levantan un servidor web de verdad e interactúan con nuestra aplicación desde un browser como lo haría un usuario. Para ello angular ofrece un DSL no demasiado complicado de manejar que nos permite indicar cosas como «navega a esta página», «pulsa este botón», «comprueba el texto de este <p>»…

Este tipo de tests es bastante más lento de ejecutar que los tests unitarios y normalmente suele haber muchos menos tests de extremo a extremo que tests unitarios. No obstante, en mi caso, en este tipo de aplicaciones escribo más tests de extremo a extremo que los que escribiría en otros entornos.

En un lenguaje estático los tipos ayudan mucho a mantener la estructura de la aplicación en pie. Si modificas el interfaz de una clase y no actualizas los clientes, la aplicación no compilará, tendrás que revisar los clientes y por lo menos tendrás una oportunidad de corregir el comportamiento. Si además tienes tests unitarios que comprueben el funcionamiento de cada clase por separado… tienes una cierta seguridad.

En javascript, si cambias el comportamiento de una clase, los clientes no se van a enterar. No hay un compilador que te diga que la llamada es incorrecta. Si tienes tests unitarios te pueden ayudar a detectar problemas, pero si la clase cliente ha sustituido la dependencia por un mock/fake/stub, todos los tests seguirán pasando sin errores pese a los cambios (esto en un lenguaje estático no ocurriría porque al cambiar el interface del componente tendrías que actualizar el mock/fake/stub y tendrías una pista de que hay algo que ajustar).

Si a esto le unimos la cantidad de configuración que va en el propio HTML a través de directivas y filtros aplicados, los tests de extremo a extremo se convierten en algo indispensable para garantizar el comportamiento del sistema.

Resumen

Por suerte, angular está bien diseñado para permitirnos testear nuestras aplicaciones. Ya he dicho antes que para mi es un factor crítico a la hora de elegir un framework y en este caso estoy muy contento con el soporte que da angular para ello.

Con este post termino el repaso a las características que me parecen más interesantes de angular. Aprovecharé el último post de la serie para hacer una pequeña valoración (aun) más personal de este framework que está tan de moda (ahora).