Las últimas semanas hemos hablado de testing funcional, así que esta semana me gustaría traducir un artículo de InfoQ titulado “Functional GUI Testing Automation Patterns”( http://www.infoq.com/articles/gui-automation-patterns)
El proceso de desarrollo de una suite automática de pruebas funcionales para una aplicación específica no es muy diferente del proceso de creación del programa en sí. Existen muchas herramientas y cada días se crean nueva para conseguir interactuar con el sistema bajo prueba (SUT).
Hoy en día existen multitud de metodologías y enfoques para enfrentarnos al desarrollo software: desde la programación orientada a objetos, la programación funcional, diseño guiado por dominio (domain drive desing), desarrollo guiado por pruebas (TDD), desarrollo guiado por comportamiento (Beharvior Driven Development BDD), etc.
En este caso vamos a dirigirnos hacia las pruebas de interfaz (GUI) cuando el sistema bajo prueba (SUT) se presenta como una caja negra para el tester de aplicaciones.
La elaboración y definición de un conjunto de mejores prácticas son una de las partes más críticas del desarrollo de pruebas automatizadas. En la siguiente imagen podemos ver como interacciona un tester de manera tradicional cuando se enfrenta a un nuevo proyecto.
Vemos que el centro del sistema es una persona con el papel de tester. El tester replica los escenarios descritos en los «test case» e interactúa manualmente con la aplicación (SUT) En caso de encontrar un comportamiento anómalo se registra dicha incongruencia en un sistema de ticketing.
El principal objetivo de la automatización de pruebas es la eliminación (o al menos minimización) de la interacción humana con el SUT. Esto es un problema muy común en los ciclo de desarrollo de producto con entrega continua.
Llegados a este punto vemos que es necesario automatizar para no herir al work in progress, por ello existen multitud de suites comerciales que nos ayudan a automatizar todas estas pruebas. En consecuencia es necesario huir del marketing y ser consecuentes con el hecho de que al automatizar una serie de casos de prueba también es necesario mantener dichas baterías.
Patrones de prueba funcional automatizada
Vemos ahora un caso típico de solución automatizada.
En el esquema de la aplicación tenemos una página de login. Cada prueba debe pasar por dicha página para llevar a cabo más funcionalidad.
En la siguiente imagen podemos ver todos los patrones que describiremos más adelante.
Implementación de patrones de test
Recorded
La prueba de la aplicación se realiza mediante una herramienta automatizada que realiza la grabación de las acciones del tester. Hasta cierto punto se considera una mala práctica debido a un mantenimiento costoso de las baterías.
Scripted
La prueba se lleva a cabo utilizando las APIs de las herramientas de automatización (Por ejemplo la API de Selenium 2 webdriver)
Plantilla de test básico (Test template)
Se implementa una plantilla de pruebas básica. Las distintas pruebas son creadas por herencia y extensión de la clase plantilla.
Data driven implementation
Cada prueba se define como un caso, para variar el test se varían los parámetros de entrada.
Este es el enfoque que se le da las pruebas unitarias.
Keyword driven implementation
Los test se realizan ayudándonos de palabras clave (clic, enter…) El tes se realiza con IDE’s especiales que permiten conectar la interfaz de la aplicación.
Existen varias herramientas para este cometido,. Los pasos de la prueba se presentan como una combinación de palabras clave, unos parámetros de entrada y un nombre de control de pantalla. Un ejemplo de ello sería HP QuickTestPro o MonkeyTalk.
Model Driven implementation
Cualquier aplicación en un preciso momento con unos datos de entrada específicos solo puede estar en un estado específico. Basándonos en esta definición podemos imaginar el software como una máquina de estados finito, así que viendo la disponibilidad de transiciones de la figura de arriba podemos definir unos conjuntos de transiciones entre páginas (flujos de trabajo) que cubra la mayor parte de la funcionalidad de la aplicación.
Implementación de patrones arquitectónicos
Multi Layered Test Solution
Se divide la lógica del sistema de pruebas en capas lógicas distintas.
Es algo normal dividir el software en capas. Capa de presentación, capa de negocio y capa de datos. Utilizando este enfoque podemos dividir los casos de test en tres capas.
Meta Framework
Este patrón define un conjunto de clases de utilidad básicas e independientes que son genéricas para cualquier herramienta de automatización y se pueden reutilizar en diferentes proyectos.
Se utiliza este patrón cuando tenemos varios proyectos y necesitamos unificar los resultados de las pruebas. También es utilizado para intentar reutilizar código entre proyectos.
Patrones de composición funcional
Método funcional
Este patrón abstrae las funciones de negocio específicas. En las diferentes suites de automatización esto suele llamarse “escenarios grabados”, es cuando un desarrollador realiza ciertas acciones con una aplicación y se crea un script de la prueba.
Ejemplo: Cambiar la apariencia de la página de login requerirá cambios en todos os escenarios. Si abstraemos el método de inicio de sesión y utilizamos este método en todas las prueba nuestros tests serán casi inmunes a los cambios de interfaz.
Page Object
Son grupos de patrones de métodos funcionales de una determinada página.
Con el fin de mejorar el mantenimiento del código del patrón método funcional se agrupan los métodos por páginas. Así por ejemplo la página de login tendrá los métodos login(), la página home tendrá los métodos logout() CreateUser().
Librería Funcional
Agrupa los objetos funcionales y/o métodos funcionales en un módulo para su reautilización
Objetos SUT: Startup-Setup y teardown (SUT Runner)
Permite la puesta en marcha del sistema bajo prueba. Después dela prueba se libran los recursos asociados.
Entre los métodos funcionales podemos distinguir un conjunto de aquellos que no están relacionados con las pruebas de funcionalidad: por ejemplo abrir un navegador y/o cerrarlo. El SUT Runner es el encargado de estas actividades generales.
Objecto Source
Son objetos que se crean ya que son necesarios para la ejecución de pruebas.
Transporter (Navigator)
Este objeto encapsula toda la lógica asociada a la navegación en el sistema, de modo que la lógica de negocio no interfiera en la navegación.
Para el caso que nos ocupa tendremos un Transporter con los métodos NavigateToLogin(), NavigateToHome(), NavigateToCreateUser() Por otra parte tendremos los PageObjects .
Composite Page Object
Sirve para reutilizar los page objects en un objeto externo,
Este modelo permite la estructuración de page objects de na manera más “orientada a objetos” mediante la separación en objetos secundarios que pueden ser reutilizados
Extended Page Login
Se extiende el page object a través de herencia, es una alternativa al Composite page object.
Patrones de procesos
Given/when/then
Se divide el proceso de ejecución en 3 etapas: dado (condiciones previas) Cuando(operaciones específicas) entonces (Revisión de resultados)
Test 4 estaciones
Se divide el proceso en 4 etapas: definición de condiciones previas, llamar a lógica de negocio, chequear el resultado, desmontar el sistema.
Flow test
Permite realizar test de lógica de negocio y chequear en una sola prueba.
Multiples fallos
Define un mecanismo que permite continuar con la prueba aunque haya un fallo no crítico.
Patrones de dependencia entre test
Standalone test
Devuelve el sistema probado en el mismo estado de antes de la prueba.
Chained test
La prueba preliminar establece el estado del SUT necesario para la siguiente prueba.
Patrones de agrupación de test
Un test por clase
Un método de prueba por cada clase de pruebas.
Agrupar tests por clase
Múltiples métodos de prueba en una clase de prueba.
Conclusiones
Es necesario establecer una serie de patrones y buenas prácticas en el desarrollo de test para poder automatizar el desarrollo de software y para obtener más consistencia en la fase de testing.