Aunque Behat 3 hace tiempo que fue lanzado aún no teníamos escrito un artículo comentando como configurarlo, similar al que había sobre Behat 2. Es hora de solventarlo, así que aquí tenemos un resumen de los cambios de Behat 2.x a Behat 3.x y como afectan al uso de Behat con Drupal. Por supuesto seguimos usando Drupal Extension para integrar más cómodamente Drupal con Behat.
Los cambios de Beaht 2 a Behat 3 no son triviales. Varias cosas han cambiado que afectarán probablemente a la forma de escribir tests a la que podía uno estar acostumbrado con la versión 2. Entra las diferencias más importantes hay que destacar la ausencia de soporte para encadenamiento de pasos (su autor indica que hace que los contextos acaben siendo más complicados) y la sustitución de los subcontextos por conjuntos de contextos.
Veamos a continuación como sería un típico fichero behat.yml para Behat 3.x para comentar en más detalle los cambios que ha sufrido Behat.
default:
autoload:
'': "%paths.base%/../all/tests/behat/bootstrap"
suites:
default:
paths:
- "%paths.base%/../all/tests/behat/features/"
contexts:
- FeatureContext:
- Drupal\DrupalExtension\Context\MinkContext
- Drupal\DrupalExtension\Context\MessageContext
- Drupal\DrupalExtension\Context\DrushContext
- Drupal\DrupalExtension\Context\DrupalContext
extensions:
Behat\MinkExtension:
goutte: ~
sessions:
default:
goutte:
guzzle_parameters:
verify: false
selenium2:
wd_host: "http://127.0.0.1:9000/wd/hub"
base_url: 'http://miproyecto.me'
show_cmd: lynx %s
files_path: "ruta/a/ficheros"
Drupal\DrupalExtension:
blackbox: ~
drush_driver: "drush"
drush:
root: "/ruta/a/raiz/drupal"
api_driver: "drupal"
drupal:
drupal_root: "/ruta/a/raiz/drupal"
region_map:
content: "#content"
footer: "#footer"
header: "#header"
header bottom: "#header-bottom"
navigation: "#navigation"
highlighted: "#highlighted"
help: "#help"
bottom: "#bottom"
selectors:
message_selector: '.messages'
error_message_selector: '.messages.error'
success_message_selector: '.messages.status'
warning_message_selector: '.messages.warning'
text:
password_field: "Contraseña"
username_field: "Correo electrónico"
log_in: "Entrar"
log_out: "Cerrar sesión"
Como puede verse, la configuración es muy parecida. Al igual que Behat 2.x el fichero se inicia con la clave default. Esta clave define un perfil de Behat. En el fichero de configuración se pueden definir tantos perfiles como se necesiten, y usarlos con el parámetro -p de la línea de comandos de Behat. Normalmente con uno basta, pero puede ser interesante bajo ciertas circunstancias tener más de uno.
Los cambios respecto a Behat 3 están localizados al princpio del fichero, pasada la clave que define el perfil, concretamente en las claves autoload y suites.
La clave autoload indica el directorio a partir del cual Behat cargará las clases de contexto usadas para ejecutar los pasos definidos en los tests. En el presente ejemplo apunta al directorio %paths.base%/../all/tests/behat/bootstrap, que es donde se encontrará el fichero FeatureContext.php con el contexto principal.
La clave suites necesita una explicación más elaborada.
Suites y grupos de contextos
Suites permite definir grupos de features y contextos para diferentes conjuntos de tests. En la documentación de Behat las suites están razonablemente explicadas, y buscando un poco hay algunos ejemplos de como usar las suites para testear primero el dominio de la aplicación y luego su interfaz gráfica. Más abajo hay otro ejempo de su utilidad.
Las suites se configuran al menos mediante dos claves: path y contexts. path permite indicar el directorio donde Behatr buscará los ficheros *.feature con los tests a lanzar, y contexts contiene un listado de los contextos que deben estar disponibles durante la ejecución.
Aquí encontramos una de las diferencias más importantes con respecto a Behat 2.x, la ausencia de soporte de subcontextos. En Behat 2.x se podían añadir subcontextos al contexto principal, de forma que los pasos de los subcontextos estaban disponibles durante la ejecución del test. En Behat 3.x esto ya no es posible directamente, y lo que se hace es definir en la propia suite los contextos que deben estar disponibles. Es decir, se sustituyen los subcontextos por grupos de contextos. Cada contexto añadirá los pasos que defina, de forma que estarán disponibles para ser usados en las diferentes features de la suite. En el ejemplo de configuración mostrado se añaden varios contextos proporcionados por Drupal Extension. Más abajo se dan más detalles sobre ellos.
Una de las cosas más interesantes de usar suites y varios contextos es la posiblidad de usar diferentes implementaciones de un mismo paso. Por ejemplo, en el clásico ejemplo de un comercio electrónico podríamos definir un paso propio que fuese añadir un producto al carrito:
/**
* @Given I add the producto :arg1 to the cart
*/
public function iAddTheProductoToTheCart($arg1)
{
...
}
Es un paso que nos será muy útil. Por un lado testeará el propio proceso que realiza el usuario al añadir un producto: acceder a la página del producto, determinar las unidades, pulsar en el enlace que añade el producto al carrito y comprobar que el producto está añadido. Por otro lado, cuando queramos testear funcionalidades de la aplicación que impliquen tener un producto en el carrito podremos reusar dicho paso. Por ejemplo, imaginemos que una tienda ofrece envío gratuito cuando el usuario compra más de 3 productos distintos. Podriamos usar:
Given I add the producto "Producto bueno" to the cart And I add the producto "Producto bonito" to the cart And I add the producto "Producto barato" to the cart Then I should see "¡Enhorabuena! Al tener 3 o más productos en el carrito el envío es gratuito"
Dado que ya se ha testeado el proceso de añadir un producto al carrito no hay necesidad de volver a hacerlo. Por tanto, podríamos definir otro contexto que implementase el mismo paso I add the producto :arg1 to the cart pero añadiendo el producto programáticamente. Esto sería bastante más rápido que hacerlo por interfaz, de forma que si se usa muchas veces durante la batería de tests la ganancia de tiempo sería notable.
Así, se definirían dos suites: una podría centrarse en el carrito, usando la implementación del paso que interactúia con la web vía navegador, y otra suite podría centrase en funcionalidades ajenas que diesen por hecho que el carrito está probado. Esta segunda suite usaría el paso que añade productos (y posiblemente otros pasos de otras acciones) programáticamente.
Behat 3 y Drupal Exension
El cambio que afecta de mayor forma a Drupal extensión es la desaparición de los subcontextos. Drupal Extension 1.0 los usaba para que los módulos de Drupal definiesen subcontextos con pasos específicos al dominio del módulo. De esta forma se facilitaban los tests sobre funcionalidades que basadas en dichos módulos.
¿Qué se ha hecho en Drupal Extension 3.x para adaptarse a este cambio? Pues lo primero ha sido crear varios contextos, diviendo los pasos que añadía Drupal Extension entre esos nuevos contextos. Así podemos incluir solo aquellos contextos que sean usados en cada suite.
Es importante tener en cuenta que esos contextos no son contextos base para crear nuestro contexto personalizado (normalmente FeatureContext). ¿Por qué? Porque por ejemplo si nuestra clase FeatureContext hereda de DrupalContext ningún otro contexto podrá heredar de DrupalContext. Esto es debido a que en ese caso ambos contextos herederos de DrupalContext definirían pasos repetidos (aquellos contenidos en DrupalContext) por lo que Behat lanzará un error al encontrar esos pasos duplicados. Para crear nuestro contexto personalizado podemos usarse el contexto RawDrupalContext, que provee funcionalidades para interactuar con Drupal pero sin añadir ningún paso:
A context that provides no step definitions, but all of the necessary functionality for interacting with Drupal, and with the browser via Mink sessions.
Bien, pero, ¿si yo no defino más contextos qué problema iba a haber? Pues por un lado puede ser interesante tener varios contextos, por ejemplo podríamos querer usar un contexto de propósito general con pasos de depuración que sea usado en más de un proyecto, además del contexto FeatureContext específico del proyecto. Pero además es que Drupal Extension sigue permitiendo a los módulos de Drupal definir contextos con pasos propios orientados al testeo del módulo. Drupal Extension busca automáticamente ficheros con contextos con el equema <nombre_modulo>.behat.inc y los añade a los contextos disponibles.
Surge entonces un problema. Durpal Extension ofrece contextos de alto nivel que interactuan con Drupal. Pero al no poder extender de ellos no se pueden aprovechar esas funciones de alto nivel que ofrecen, ni por el contexto FeatureContext específico del proyecto ni por los contextos que provean los módulos. Para solventarlo, DrupalExtension ofrece la opción de acceder a otros contextos desde un contexto. Basta con añadir el siguiente código al contexto:
/**
* @BeforeScenario
*/
public function gatherContexts(BeforeScenarioScope $scope) {
$environment = $scope - > getEnvironment();
$this - > drupalContext = $environment - > getContext(
'Drupal\DrupalExtension\Context\DrupalContext'
);
$this - > minkContext = $environment - > getContext(
'Drupal\DrupalExtension\Context\MinkContext'
);
}
Así, el contexto tendría acceso a las funciones de DrupalContext como asegurar que se han limpiado las cachés o se ha ejecutado cron.
Ah, por último un detalle de configuración más: se puede indicar a Drupal Extension una o más rutas donde buscar contextos adicionales que no estén en ningún módulo:
subcontexts:
paths:
- "/ruta/a/raiz/drupal/sites/all/tests/behat/contexts"