Implementando un feature en behat

Author Top
Luis Eduardo Telaya Escobedo

0. Pre-requisitos

Revisar post ¿Qué es y Cómo instalar behat?

1. Objetivo

- Validar que nuestra app(el cual es una sencilla aplicación que suma dos valores) sea el correcto valor esperado.

Tenemos la siguiente clase:

Calculate.php con métodos setValueA, setValueB, getValueA, getValueB, Sum y getResult.

<?php

class Calculate {
  private $value_a;
  private $value_b;
  private $result;

  public function __construct() {
  }

  public function setValueA($value_a) {
    $this->value_a = $value_a;
  }

  public function setValueB($value_b) {
    $this->value_b = $value_b;
  }

  public function getValueA() {
    return $this->value_a;
  }

  public function getValueB() {
    return $this->value_b;
  }

  public function sum() {
    $this->result = $this->getValueA() + $this->getValueB();
  }

  public function getResult() {
    return $this->result;
  }
}
?>

y un archivo index.php el cual instancia Calculate y establece valores 3 y 10, los suma e imprime el valor por pantalla(el cual es 10).

<?php
include_once 'Calculate.php';

$calculate = new Calculate();
$calculate->setValueA(3);
$calculate->setValueB(7);
$calculate->sum();

print 'Result of myapp: ' . $calculate->getResult();
?>

No obstante ¿Cómo podemos validar que funcione correctamente?

En este caso usaremos behat! para validar la suma de dos números positivos. Así que vamos a escribir el feature "Addition of two numbers"

Feature: Addition of two numbers

As a student of mathematics
I want to get the sum of two numbers
To learn how to add

Scenario: Add two positive numbers
     Given I am in the application
     When I enter the numbers 7 and 3
     And I apply the calculation result
     Then the result should be 10

2. Implementación

Debemos de tener la siguiente estructura de carpetas:

├── LICENSE
├── bin
├── composer.json
├── composer.lock
├── composer.phar
├── vendor/
├── index.php
├── Calculate.php

Ahora si vamos a la raíz de nuestro proyecto veremos que hay una carpeta bin/ y adentro un archivo ejecutable behat. Ahora corremos el siguiente comando desde la raíz de nuestro proyecto:

$ bin/behat --init

Y obtendremos el siguiente resultado:

+d features - place your *.feature files here
+d features/bootstrap - place bootstrap scripts and static files here
+f features/bootstrap/FeatureContext.php - place your feature related code here

Con el comando anterior $ bin/behat --init obtendremos una estructura básica para poder colocar nuestros features.

si queremos agregar más *.features las agregamos en la carpeta features y su implementación en código php las agregamos en features/bootstrap. 

Tambien podemos ver que se ha creado features/bootstrap/FeatureContext.php es un archivo php de ejemplo donde contiene una estructura basica a implementar nuestro feature con algunos use propios de behat.

<?php

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

//
// Require 3rd-party libraries here:
//
//   require_once 'PHPUnit/Autoload.php';
//   require_once 'PHPUnit/Framework/Assert/Functions.php';
//

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
    /**
     * Initializes context.
     * Every scenario gets it's own context object.
     *
     * @param array $parameters context parameters (set them up through behat.yml)
     */
    public function __construct(array $parameters)
    {
        // Initialize your context here
    }

//
// Place your definition and hook methods here:
//
//    /**
//     * @Given /^I have done something with "([^"]*)"$/
//     */
//    public function iHaveDoneSomethingWith($argument)
//    {
//        doSomethingWith($argument);
//    }
//
}

Ahora vamos agregar features/Sum.feature que es donde pondremos lo siguiente:

Feature: Addition of two numbers

As a student of mathematics
I want to get the sum of two numbers
To learn how to add

Scenario: Add two positive numbers
     Given I am in the application
     When I enter the numbers 7 and 3
     And I apply the calculation result
     Then the result should be 10

Ejemplo de como estaria quedando nuestra directorio hasta el momento:

├── LICENSE
├── bin
├── composer.json
├── composer.lock
├── composer.phar
├── vendor/
├── index.php
├── Calculate.php
├── features/
├──── Sum.feature
├──────bootstrap/
├────────FeatureContext.php

Ahora necesitamos implementar cada step e.g: "When I enter the numbers 7 and 3"  necesitamos escribir código php de cada uno de estos pasos. Para esto se sigue un patron.

Por ejemplo "When I enter the numbers 7 and 3" en php seria:

    /**
     * @When /^I enter the numbers (\d+) and (\d+)$/
     */
    public function iEnterTheNumbersAnd($arg1, $arg2)
    {
        throw new PendingException();
    }

Notese que es camel case el nombre de la función y los números pasan como argumentos. Y en los comentarios tienen su propio patron. (en otro post veremos a detalle estos patrones)

Tenemos otra alternativa a estar creando uno por uno a php, el cual es desde la terminal ir a la raiz del proyecto y ejecutar el comando:

$ bin/behat

Y obtendremos lo siguiente:

Feature: Addition of two numbers

  As a student of mathematics
  I want to get the sum of two numbers
  To learn how to add

  Scenario: Add two positive numbers   # features/Sum.feature:7
    Given I am in the application
    When I enter the numbers 7 and 3
    And I apply the calculation result
    Then the result should be 11

1 scenario (1 undefined)
4 steps (4 undefined)
0m0.008s

You can implement step definitions for undefined steps with these snippets:

    /**
     * @Given /^I am in the application$/
     */
    public function iAmInTheApplication()
    {
        throw new PendingException();
    }

    /**
     * @When /^I enter the numbers (\d+) and (\d+)$/
     */
    public function iEnterTheNumbersAnd($arg1, $arg2)
    {
        throw new PendingException();
    }

    /**
     * @Given /^I apply the calculation result$/
     */
    public function iApplyTheCalculationResult()
    {
        throw new PendingException();
    }

    /**
     * @Then /^the result should be (\d+)$/
     */
    public function theResultShouldBe($arg1)
    {
        throw new PendingException();
    }

Con el comando anterior es para correr el feature pero dado que no hay ningun método implementado aún te muestra los métodos de cada paso que deberias implementar del o los escenario(s) y así evitamos estar haciendo el trabajo manualmente. Lo que tenemos que hacer es copiar y pegar los métodos en nuestra clase features/bootstrap/FeatureContext.php

Una vez copiado y pega tendriamos lo siguiente:

<?php

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

//
// Require 3rd-party libraries here:
//
//   require_once 'PHPUnit/Autoload.php';
//   require_once 'PHPUnit/Framework/Assert/Functions.php';
//

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
    /**
     * Initializes context.
     * Every scenario gets its own context object.
     *
     * @param array $parameters context parameters (set them up through behat.yml)
     */
    public function __construct(array $parameters)
    {
        // Initialize your context here
    }

    /**
     * @Given /^I am in the application$/
     */
    public function iAmInTheApplication()
    {
        throw new PendingException();
    }

    /**
     * @When /^I enter the numbers (\d+) and (\d+)$/
     */
    public function iEnterTheNumbersAnd($arg1, $arg2)
    {
        throw new PendingException();
    }

    /**
     * @Given /^I apply the calculation result$/
     */
    public function iApplyTheCalculationResult()
    {
        throw new PendingException();
    }

    /**
     * @Then /^the result should be (\d+)$/
     */
    public function theResultShouldBe($arg1)
    {
        throw new PendingException();
    }
}

Notese que los nombres de las funciones se genera basado en el texto de cada step escapando los numeros y aplicando el camelcase, no obstante no tienen nada implemetado aun.

El paso siguiente es escribir código en cada método de acuerdo al paso. Vamos a usar require_once para llamar a Calculate.php

en iAmInTheApplication()  vamos a instanciar el objeto $calculate = new Calculate()

iEnterTheNumbersAnd($arg1, $arg2) vamos a setear setValueA($arg1) y setValueB($arg2)

iApplyTheCalculationResult() usaremos el método sum()  

theResultShouldBe($arg1) validaremos usando getResult() si la suma fue correcta o no. Si no fuera correcta lanzaremos una exception.

Nota: los nombres de los métodos hacen match a cada step, si los modifica ya no funcionara y dara un mensaje de error.

Así quedaria nuestra clase features/bootstrap/FeatureContext.php

<?php

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

require_once __DIR__ . '/../../Calculate.php';

//
// Require 3rd-party libraries here:
//
//   require_once 'PHPUnit/Autoload.php';
//   require_once 'PHPUnit/Framework/Assert/Functions.php';
//

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
  public $calculate;

    /**
     * Initializes context.
     * Every scenario gets it's own context object.
     *
     * @param array $parameters context parameters (set them up through behat.yml)
     */
    public function __construct(array $parameters)
    {

    }

    /**
     * @Given /^I am in the application$/
     */
    public function iAmInTheApplication()
    {
        $this->calculate = new Calculate();
    }

    /**
     * @When /^I enter the numbers (\d+) and (\d+)$/
     */
    public function iEnterTheNumbersAnd($arg1, $arg2)
    {
      $this->calculate->setValueA($arg1);
      $this->calculate->setValueB($arg2);
    }

    /**
     * @Given /^I apply the calculation result$/
     */
    public function iApplyTheCalculationResult()
    {
      $this->calculate->sum();
    }

    /**
     * @Then /^the result should be (\d+)$/
     */
    public function theResultShouldBe($arg1)
    {
      if ($this->calculate->getResult() != $arg1) {
        throw new Exception("Error in the addition");
      }
    }
}

2.1 Test aprobado!

Ahora vamos a la raíz de nuestro proyecto corremos el siguiente comando en nuestra terminal:

$ bin/behat

Con el comando anterior va a correr la feature y va paso a paso, lee el paso 1 Dado que esto en la aplcación, cuando ingreso números 7 y 3 y calculo, luego el resultado debe ser 10. Si hubiese un paso nuevo nos indicara que debemos implementarlo y nos dara los comentarios y el nombre del método ya en camelcase listo para realizar copy & paste.

Y en nuestro caso Obtendremos:

Feature: Addition of two numbers

  As a student of mathematics
  I want to get the sum of two numbers
  To learn how to add

  Scenario: Add two positive numbers   # features/Sum.feature:7
    Given I am in the application      # FeatureContext::iAmInTheApplication()
    When I enter the numbers 7 and 3   # FeatureContext::iEnterTheNumbersAnd()
    And I apply the calculation result # FeatureContext::iApplyTheCalculationResult()
    Then the result should be 10       # FeatureContext::theResultShouldBe()

1 scenario (1 passed)
4 steps (4 passed)
0m0.007s

 Felicidades el escenario paso correctamente! quiere decir si sumamos 7 + 3 entonces dara como resultado 10

2.2 Test que falla!

Ahora haremos que el test falle

Para eso editaremos features/Sum.feature y le pondremos Then the result should be 11 en vez de 10. Así quedaria:

Feature: Addition of two numbers

As a student of mathematics
I want to get the sum of two numbers
To learn how to add

Scenario: Add two positive numbers
     Given I am in the application
     When I enter the numbers 7 and 3
     And I apply the calculation result
     Then the result should be 11

Ahora si vamos a la raíz de nuestro proyecto corremos el siguiente comando en nuestra terminal:

$ bin/behat

Obtendremos:

Feature: Addition of two numbers

  As a student of mathematics
  I want to get the sum of two numbers
  To learn how to add

  Scenario: Add two positive numbers   # features/Sum.feature:7
    Given I am in the application      # FeatureContext::iAmInTheApplication()
    When I enter the numbers 7 and 3   # FeatureContext::iEnterTheNumbersAnd()
    And I apply the calculation result # FeatureContext::iApplyTheCalculationResult()
    Then the result should be 11       # FeatureContext::theResultShouldBe()
      Error in the addition

1 scenario (1 failed)
4 steps (3 passed, 1 failed)
0m0.024s

Fallo nuestro scenario  en el paso Then the result should be 11 ya que la suma de 7 + 3 no es 11 sino 10. Lanzando la exception "Error in the addition"

y es así como implementamos behat!

Este fue un ejemplo muy sencillo de como usar behat . En otro post veremos como implementar MINK donde crearemos un ejemplo parecido pero esta vez con un formulario html donde se ingresen estos valores y otros ejemplos donde le sacaremos al máximo a behat!  además de posts integrandolo con Drupal! lo que esperaban!.

Happy codding