Todos los que seguís este blog sabéis que me interesa mucho buscar la mejor forma de testear aplicaciones y que considero que unos buenos tests son fundamentales para conseguir aplicaciones más sólidas y dormir más tranquilo. A lo largo de los posts he hablado mucho sobre tests unitarios, tests de aprobación e incluso he tocado por encima los tests de extremo a extremo.
En este post vamos a ver una nueva herramienta para escribir tests sobre páginas web que me ha llamado especialmente la atención: canopy.
¿Qué es canopy?
Canopy es una herramienta open source que nos permite escribir tests sobre sitios web usando un DSL desarrollada en F#. Internamente utiliza Selenium, lo que nos permite testear directamente sobre distintos navegadores reales.
La documentación de canopy, aunque escueta, es lo bastante clara como para poder empezar a escribir tests sin mucho esfuerzo y, aunque no soy un gran fan de sintaxis de F#, tengo que reconocer que el DSL está muy logrado y permite escribir tests bastante claros.
Para usarlo sólo es necesario instalarlo desde Nuget. Esto podemos hacerlo en cualquier proyecto de .NET (sí, también C#), pero merece la pena escribir los tests en F#, por lo que lo más recomendable es escribir los tests en un proyecto de consola de F#.
Testeando MegaFacts
Como toma de contacto, vamos a usar canopy para testear la aplicación de ejemplo MegaFacts que he usado tanto en el tutorial de node.js y express, como en el tutorial de compojure, concretamente, testearemos la implementación de compojure.
Lo importante es que tengáis en cuenta que, puesto que estamos testeando desde el propio interface web, nos da igual cómo esté implementado por detrás, ya que estaremos interactuando directamente con el navegador.
La página principal (y única) de MegaFacts era algo así:
A la izquierda se muestra una lista de «héroes» sobre los que podemos pulsar para mostrar los «facts» asociados a cada uno y añadir nuevos facts. Es importante reseñar que la página en sí no está pensada para ser testeada, pero aun así podremos conseguir unos tests relativamente legibles.
Los tests los vamos a organizar en un módulo llamado MegaFacts
en el que lo primero que haremos será incluir los módulos que necesitamos:
module MegaFacts open canopy open runner
A continuación, añadiremos un Page Model para independizar un poco los tests de la estructura real de la página:
let factsUrl = "http://localhost:3000" // Enlaces a los diferentes héroes let heroArturo = "#heroes li:nth-of-type(1) a" let heroBruce = "#heroes li:nth-of-type(2) a" let heroChuck = "#heroes li:nth-of-type(3) a" // Información sobre el héroe selecciando let currentHeroName = "#right-column h2" let currentFacts = "#facts li" // Controles para añadir facts let newFactText = "#new-fact" let addNewFact = "#add-new-fact"
En el Page Model estamos «poniendo nombres» a los diferentes elementos que componen la página, los cuales identificamos mediante selectores CSS. Aquí se nota que la página no está diseñada para ser testeada y hace falta usar algunos selectores un poco rebuscados, pero al menos podemos testearla y, gracias al Page Model, si la cambiamos la estructura de la página para adecentarla un poco, sólo necesitaremos actualizar el Page Model y nuestros tests seguirán funcionando.
Ahora que ya tenemos todo preparado, vamos a escribir nuestro primer test, que consistirá en validar que al pulsar sobre nombre de cada héroe se muestran sus facts asociados:
let browseFacts _ = context "Consultando los facts de cada héroe" url factsUrl displayed "#heroes" "Al seleccionar un héroe se muestran sus facts" &&& fun _ -> click heroChuck currentHeroName == "Chuck Norris" currentFacts *= "Chuck Norris borró la papelera de reciclaje." click heroArturo currentHeroName == "Arturo Pérez-Reverte" currentFacts *= "Pérez-Reverte se baja música en casa de Ramoncín."
En este código podemos ver en acción el DSL de canopy. Usando context
podemos indicar qué es lo que estamos testando y con la función (infija) &&&
definimos nuestros tests. Veis que tenemos acciones, como url
que nos permite navegar a una página o click
para interactuar con un elemento, y aserciones, como displayed
para comprobar que se muestra un elemento, ==
para validar el texto de un elemento, o *=
que valida si alguno de los elementos de la colección contiene el texto indicado.
Vamos a ver otro test en el que comprobemos que podemos añadir nuevos facts a un héroe:
let addFact _ = context "Añadiendo un nuevo fact" "Al añadir un nuevo fact se muestra en la lista de facts" &&& fun _ -> click heroChuck currentHeroName == "Chuck Norris" newFactText << "Object hereda de Chuck Norris" click addNewFact currentFacts *= "Object hereda de Chuck Norris"
La única novedad de este test con respecto al anterior es la función <<
para establecer el texto de un control, en este caso el textarea
usado para introducir el nuevo fact.
Por conveniencia, todos los tests de este módulo los podemos agrupar en una única función que se encargue de ejecutarlos secuencialmente:
let all _ = browseFacts() addFact()
Si tuviésemos más páginas en nuestra aplicación podríamos definir módulos para cada una de ellas, cada uno de ellos con su función all
para agruparlos. Finalmente, para ejecutar los tests, en el punto de entrada de nuestra aplicación de consola tendremos algo así:
open canopy open runner start firefox MegaFacts.all() run()
Simplemente abrimos un navegador (en este caso Firefox), definimos todos los tests de los módulos que tengamos y usamos la función run
para ejecutarlos.
Para ejecutarlos, necesitamos que la página tiene que estar accesible (podríamos incluso levantar el servidor donde está alojada en el propio test) y ejecutar la aplicación de consola que acabamos de crear. Veréis como se abre un navegador y se empieza a interactuar con él para completar las acciones que hemos definido en los tests.
El resultado será algo así:
Por si queréis verlo más detenidamente, aquí os dejo el código completo del ejemplo y recordad que para ejecutarlo hace falta lanzar el servidor web de MegaFacts.
Conclusiones
Canopy es un proyecto muy interesante para realizar tests de extremo a extremo. Su sintaxis resulta clara, sencilla y natural. Como habéis visto en el ejemplo, es posible testear sin mucha dificulta páginas que, en un principio no estaban diseñadas para ser testeadas (aunque es verdad que pensar un poco en los tests a la hora de diseñar las págians simplifica luego el mantenimiento de los tests).
Una de las cosas que más me gusta de Canopy es que está desarrollado en F#. No es tanto por F# en si (todavía no tengo muy claro si me gusta o no el lenguaje), pero el hecho de ver que .NET es cada vez más políglota me hace tener más esperanzas en el futuro de la plataforma.
Pingback: F# Weekly #15, 2014 | Sergey Tihon's Blog
Muy buen artículo.
¿Existe alguna manera, configuración, etc, para decirle a canopy que solo quiero correr determinada prueba?
Si defines un test como «work in progress», usando &&&& en lugar de &&&, sólo se ejecutará ese. Más info aquí: http://lefthandedgoat.github.io/canopy/testing.html
Excelente!!!