Lenguajes de Dominio Específico para el Desarrollo de Pruebas Automatizadas

Publicado en

La diferencia entre un producto bueno y uno excelente es la calidad; a medida que una aplicación crece, es mayor el esfuerzo que debemos invertir para validar la funcionalidad. Las pruebas automatizadas nos ahorran tiempo y dinero, estas pueden ser repetidas una y otra vez durante todo el ciclo del desarrollo para asegurar la calidad del producto. Cada vez que exista un cambio, arreglo o nuevos elementos, seremos capaces de validar la funcionalidad completa, rápida y consistentemente. Nuestros casos de prueba deben poder ser ejecutados con diversos conjuntos de datos en diversos entornos, sin que esto represente un esfuerzo adicional para darles mantenimiento a medida que el proyecto crece.

Las pruebas automatizadas son por sí mismas aplicaciones cuyo objetivo es validar la funcionalidad del software, por lo cual es necesario que apliquemos el mismo enfoque que el usado para crear el producto. Para demostrar cómo es que podemos diseñar pruebas altamente efectivas, haremos uso de un lenguaje específico para pruebas junto con el patrón PageObject. Los ejemplos que muestro a continuación están enfocados a la validación de aplicaciones web, sin embargo, estos mismos principios pueden ser utilizados en pruebas para aplicaciones de escritorio o móviles.

El patrón PageObject

Este patrón de diseño nos permite mapear los elementos de la página en un objeto al igual que las acciones que podemos realizar dentro de la misma: agregar usuarios, actualizar descripciones de productos, etc. En esencia este modelo es una forma especializada del patrón Façade, lo que en términos simples significa que reemplazamos una API poco descriptiva por una que es más fácil de entender. Veamos el siguiente ejemplo para ilustrar este concepto, el código está escrito en Java usando Selenium:

WebElement formDiv = driver.findElement(By.className(“form-group”));
assertEquals(formDiv.findElement(By.xpath(“//input[3]”)).getText(),”Guitar”);

En este fragmento de código, el API está orientado hacia los elementos HTML; ahora veamos el mismo ejemplo haciendo uso del patrón antes descrito:

assertEquals(WishList.Description, “Guitar”);

Como podemos observar tenemos un objeto que modela nuestra página de WishList la cual cuenta con la descripción del producto como propiedad, aquí es más claro qué es lo que estamos validando, más importante aún, nuestro modelo nos permite abstraer la implementación específica. En el primer ejemplo, si se agrega un campo más a la aplicación, la descripción no se encontraría en el tercer input y actualizar nuestra prueba implicaría cambiar todas las partes donde aparece “//input[3]”, pero al hacer uso de un modelo solo requerimos modificar el selector en un solo lugar, lo cual facilita dar mantenimiento a nuestras pruebas. En un escenario ideal en el cual tanto las pruebas como la aplicación comparten un mismo diccionario de selectores, al momento de que el desarrollador modifique el mismo selector, las pruebas quedarían automáticamente actualizadas; sin embargo, esto requiere una estrecha colaboración entre el equipo de calidad y los desarrolladores. Pero aun cuando no contemos con este elemento común entre aplicación y pruebas, podemos ver las ventajas que nos ofrece este patrón:

  • El modelo representa las pantallas de la aplicación web como una serie de objetos y encapsula las características representadas por una página.
  • Nos permite generar pruebas inteligibles y robustas.
  • Reduce la duplicidad del código en las pruebas.
  • Simplifica el mantenimiento a largo plazo.

Este patrón de diseño nos permite crear un lenguaje específico para el desarrollo de pruebas automatizadas. Citando a Martin Fowler:

“Creo que la parte más difícil de los proyectos de software, la fuente más común de fracaso del proyecto, es la comunicación con los clientes y usuarios de ese software. Al proporcionar un lenguaje claro y preciso para hacer frente a los dominios, un DSL puede ayudar a mejorar esta comunicación. “

En este caso, nuestro dominio son las pruebas, hasta ahora solo hemos creado el modelo de una página, podemos usar este mismo principio para sacar mayor provecho y definir como tal un DSL (Domain Specific Language), el siguiente ejemplo presenta una prueba que hace uso del mismo:

I Login TestCase.UserName TestCase.Password
And Create TestCase.NewProduct
And Save

La prueba cumple con todos los requisitos que hemos planteado, ahora nos daremos a la tarea de descomponerla para ver cómo es que logramos llegar a este punto. Para este ejemplo en particular estamos usando F#, FluentAutomation y distintos modelos, uno para la página y otro para los datos.

FluentAutomation

Este framework puede ser usado con Selenium WebDriver o WatiN, lo que nos permite realizar pruebas con distintos navegadores y dispositivos. Hemos seleccionado esta herramienta por su flexibilidad y porque ya incluye en su API la habilidad para implementar fácilmente el patrón PageObject. En este artículo no vamos a profundizar en todas las características de este framework, para lo cual recomiendo al lector revisar la documentación del mismo. Continuando con nuestro ejemplo, presentamos el modelo de la página escrito en C#:

public class SalesLoginPage : PageObject {
  public SalesLoginPage(FluentTest test) : base(test) {
    Url = string.Format(“{0}/{1}”, TestCase.Environment, “/sales/login”);
    At = () =>;
    I.Expect.Exists(SalesLoginElements.UserNameInput);
    I.Expect.Exists(SalesLoginElements.PasswordInput);
  }
  public SalesHomePage login(string userName, string password) {
    I.Enter(userName).In(SalesLoginElements.UserNameInput);
    I.Enter(password).In(SalesLoginElements.PasswordInput);
    I.Click(SalesLoginElements.LoginButton);
    return Switch<SalesHomePage>();
  }
}

Analicemos un poco este código antes de avanzar. Primero vamos a hablar del objeto TestCase, el cual es estático sin embargo los valores de sus propiedades los leemos desde nuestra fuente de datos. Para este ejemplo usamos un archivo XML, pero podría usarse otra fuente distinta (base de datos, JSON, CSV, etcétera), esto nos permite ejecutar las pruebas en diferentes entornos (local, instancia de pruebas, pre-producción) o distintos usuarios.

El objeto SalesLoginElements es un recurso (Archivo *.resx) en el cual mantenemos la lista de selectores, tal como hemos mencionado anteriormente. Esta lista nos permite facilitar el mantenimiento de las pruebas cuando hay cambios en la página, e incluso puede ser compartida con la aplicación web.

SalesLoginPage y SalesHomePage son modelos que heredan de PageObject. Aquí hemos utilizado la convención de nombrar las acciones que podemos realizar en la página con minúsculas, esto lo podemos ver en la prueba original y solo es una nomenclatura para diferenciar estas acciones de las funciones propias del DSL (I, And, etcétera).

Escribiendo pruebas en F#

Ahora que ya tenemos nuestros modelos, diccionarios y fuentes de datos, sólo nos resta escribir las suites de pruebas. Para lograr esto optamos por un lenguaje funcional, ya que nos permite escribir código expresivo que podemos ejecutar de manera interactiva; F# provee la sintaxis que nos permite escribir pruebas a modo de especificaciones. Tal como algunas herramientas para BDD (behavior-driven development), estas pruebas son legibles para más personas, además de los desarrolladores e ingenieros de calidad.

El proyecto con código fuente completo para este artículo está disponible en nuestro GitHub: https://github.com/ScioMx

Conclusión

Como ya hemos visto en los ejemplos, el crear pruebas automatizadas que sirvan como herramienta para el control de calidad, utilizando el patrón PageObject y un lenguaje específico para pruebas, es una tarea sencilla.

Es muy importante mencionar que aún cuando usemos otros lenguajes, frameworks o herramientas distintos a los de los ejemplos presentados en este artículo, es posible lograr el mismo resultado haciendo uso de los conceptos clave. Para ello me gustaría mencionar algunos frameworks/herramientas que nos permiten definir DSLs: Coulda (Ruby), Jasmine (JS), PyParsing (Python), Apache Camel (Java), o inclusive Parrot.

Referencias

[1] Martin Fowler: PageObject. http://martinfowler.com/bliki/PageObject.html
[2] Martin Fowler: DSL. http://martinfowler.com/bliki/DomainSpecificLanguage.html
[3] Fluent Automation. http://fluent.stirno.com/docs
[4] BDDfy your browser testing with FluentAutomation. http://blog.chatekar.com/bddfy-your-browser-automation-with-fluentautomation
[5] Martin Fowler: FluentInterface. http://martinfowler.com/bliki/FluentInterface.html
[6] Selenium Project. http://code.google.com/p/selenium/wiki/PageObjects
[7] Herramientas:
  https://github.com/elight/coulda
  http://jasmine.github.io/
  http://pyparsing.wikispaces.com/
  http://camel.apache.org/
  http://www.parrot.org/

Bio

Ernesto Herrera es Ingeniero en Sistemas con más de 10 años de experiencia. Se desempeña como desarrollador líder en Scio Consulting, empresa dedicada a planear, diseñar, desarrollar y operar aplicaciones con servicios basados en Internet. Junto con el equipo de Scio, Ernesto ha trabajado en diversos proyectos para realizar pruebas unitarias, automatizadas y de integración.