Testeando sitios web con F# y Canopy

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í:

Mockup de la página de Mega Facts

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í:

canopy-output

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.

4 comentarios en “Testeando sitios web con F# y Canopy

  1. Pingback: F# Weekly #15, 2014 | Sergey Tihon's Blog

  2. Muy buen artículo.
    ¿Existe alguna manera, configuración, etc, para decirle a canopy que solo quiero correr determinada prueba?

Comentarios cerrados.