phpspec

Al igual que Behat, phpspec es un marco de prueba de código abierto y gratuito basado en la noción de BDD. Sin embargo, su enfoque para las pruebas es bastante diferente al de Behat; incluso podemos decir que se encuentra en algún lugar en el medio de PHPUnit y Behat. A diferencia de Behat, phpspec no usa las historias en formato Gherkin para describir sus pruebas. Al hacerlo, phpspec cambia su enfoque en el comportamiento de la aplicación interna, en lugar de la externa. Al igual que PHPUnit, phpspec nos permite instanciar objetos, llamar a sus métodos y realizar varias afirmaciones sobre los resultados. La parte en la que difiere es en su enfoque de «pensar en la especificación» y no en el de «pensar en la prueba».

Configurando phpspec

Al igual que PHPUnit y Behat, phpspec se puede instalar como herramienta y biblioteca. La versión de la herramienta es el archivo .phar, podemos descargarla del repositorio oficial de GitHub, mientras que la versión de la biblioteca viene empaquetada como un paquete Composer.
Asumiendo que estamos usando la instalación de Ubuntu 16.10 (Yakkety Yak), instalar phpspec como herramienta es fácil, como se muestra en los siguientes comandos:

wget
https://github.com/phpspec/phpspec/releases/download/3.2.3/phpspec.phar
chmod +x phpspec.phar
sudo mv phpspec.phar /usr/local/bin/phpspec
phpspec --version

Esto debería darnos el siguiente resultado

Instalar phpspec como biblioteca es tan fácil como ejecutar el siguiente comando de consola dentro de la raíz de nuestro proyecto:

composer require phpspec/phpspec

Esto debería darnos el resultado final, que se parece a la siguiente captura de pantalla:

La biblioteca phpspec ahora está disponible en el directorio vendor/phpspec y su herramienta de consola es ejecutable en el archivo vendor/bin/phpspec.

Examen de escritura

Comenzar a escribir pruebas phpspec requiere comprender algunos conceptos básicos, como los siguientes:

  • Los métodos it _ * () y its_*(): este comportamiento de objeto se compone de ejemplos individuales, cada uno marcado con los métodos it _ *() o sus métodos _ *(). Podemos tener uno o más de estos métodos definidos por especificación única. Cada método definido se activa cuando se ejecuta una prueba.
  • Métodos Matchers: son análogos a las afirmaciones en PHPUnit. Describen cómo debe comportarse un objeto.
  • Métodos de construcción de objetos: cada objeto que describimos en phpspec no es una variable separada, sino $this. A veces, sin embargo, obtener el propia variable $this requiere la gestión de parámetros del constructor. Aquí es donde los métodos beConstructedWith(), beConstructedThrough(), let() y letGo() son útiles.
  • El método let(): esto se ejecuta antes de cada ejemplo.
  • El método letGo(): se ejecuta después de cada ejemplo.

Es probable que los matchers sean algo con lo que tendremos más contacto, por lo que vale la pena saber que hay varios matchers diferentes en phpspec, todos los cuales implementan la interfaz Matcher declarada en el archivo src\PhpSpec\Matcher\Matcher.php:

<?php
namespace PhpSpec\Matcher;
interface Matcher
{

public function supports($name, $subject, array $arguments);

public function positiveMatch($name, $subject, array $arguments);

public function negativeMatch($name, $subject, array $arguments);

public function getPriority();
}

Usando el comando phpspec describe, podemos crear una especificación para una de las clases concretas existentes o nuevas que aún tenemos que escribir. Como ya tenemos nuestro conjunto de proyectos, sigamos adelante y generemos una especificación para nuestras clases de Cart y Product.
Lo haremos ejecutando los siguientes dos comandos dentro del directorio raíz de nuestro proyecto:

hpspec describe Foggyline/Checkout/Model/Cart
phpspec describe Foggyline/Catalog/Model/Product

El primer comando genera el archivo spec/Foggyline/Checkout/Model/CartSpec.php,
con su contenido inicial como sigue:

<?php
namespace spec\Foggyline\Checkout\Model;
use Foggyline\Checkout\Model\Cart;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class CartSpec extends ObjectBehavior
{

function it_is_initializable()
{

$this->shouldHaveType(Cart::class);
}
}

El segundo comando genera el archivo spec/Foggyline/Catalog/Model/ProductSpec.php, con su contenido inicial de la siguiente manera:

<?php
namespace spec\Foggyline\Catalog\Model;
use Foggyline\Catalog\Model\Product;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ProductSpec extends ObjectBehavior
{

function it_is_initializable()
{

$this->shouldHaveType(Product::class);
}
}

Las clases CartSpec y ProductSpec generadas son casi idénticas. La diferencia radica en las clases concretas a las que hacen referencia mediante la llamada al método shouldHaveType(). En el futuro, intentaremos escribir algunas pruebas simples solo para los modelos de Cart y Product.
Dicho esto, avancemos y modifiquemos nuestras clases CartSpec y ProductSpec para reflexionar sobre el uso de matchers: las funciones it _ *() y its _ *().
Modificaremos el archivo spec\Foggyline\Checkout\Model\CartSpec.php con el siguiente contenido:

<?php
namespace spec\Foggyline\Checkout\Model;
use Foggyline\Checkout\Model\Cart;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Foggyline\Catalog\Model\Product;
class CartSpec extends ObjectBehavior
{
function it_is_initializable()
{

$this->shouldHaveType(Cart::class);
}

function it_adds_single_product_to_cart()
{

$this->addProduct(

new Product('YL', 'Yellow Laptop', 1499.99, 25),

2

);

$this->count()->shouldBeLike(1);
}

function it_adds_two_products_to_cart()
{

$this->addProduct(

new Product('YL', 'Yellow Laptop', 1499.99, 25),

2

);

$this->addProduct(

new Product('RL', 'Red Laptop', 2499.99, 25),

2

);

$this->count()->shouldBeLike(2);
}
}

Modificaremos el archivo spec\Foggyline\Catalog\Model\ProductSpec.php con el siguiente contenido:

<?php
namespace spec\Foggyline\Catalog\Model;
use Foggyline\Catalog\Model\Product;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ProductSpec extends ObjectBehavior
{

function it_is_initializable()
{

$this->shouldHaveType(Product::class);
}

function let()
{

$this->beConstructedWith(

'YL', 'Yellow Laptop', 1499.99, 25

);
}

function its_price_should_be_like()
{

$this->getPrice()->shouldBeLike(1499.99);
}

function its_title_should_be_like()
{

$this->getTitle()->shouldBeLike('Yellow Laptop');
}
}

Aquí, estamos utilizando el método let(), ya que se dispara antes de que se ejecute cualquiera de los métodos it _ *() o its_ *(). Dentro del método let(), estamos llamando a beConstructedWith() con argumentos que normalmente pasaríamos a la expresión new
Product (…). Esto construye nuestra instancia de producto y permite que todos los métodos it _ *() o its_ *() se ejecuten con éxito.

Echa un vistazo a http://www.phpspec.net/en/stable/manual/introduction.html para obtener más información sobre los conceptos avanzados de phpspec.

Ejecutando pruebas

Ejecutar solo un comando de ejecución de phpspec en este punto probablemente fallará con algo así como un mensaje de clase … no existe, porque phpspec asume una asignación PSR-0 por defecto. Para poder trabajar con la aplicación que hemos hecho hasta ahora, necesitamos decirle a phpspec que incluya nuestras clases src/Foggyline/ *. Podemos hacerlo a través de un archivo de configuración phpspec.yml, o usando la opción –bootstrap. Como ya hemos creado el archivo autoload.php, avancemos y ejecutemos phpspec iniciando este archivo de la siguiente manera:

phpspec run --bootstrap=autoload.php

Esto genera el siguiente resultado:

Hemos involucrado estas dos especificaciones usando phpspec describe en la clase existente. Podríamos pasar fácilmente el nombre de clase no existente al mismo comando, según el siguiente ejemplo:

phpspec describe Foggyline/Checkout/Model/Guest/Cart

La clase Guest\Cart no existe realmente en nuestro directorio src/.phpspec no tiene problemas para crear un archivo de especificación spec/Foggyline/Checkout/Model/Guest/CartSpec.php, tal como lo hizo para Cart y Product. Sin embargo, ejecutar phpspec describe ahora genera una clase … no existe un mensaje de error, junto con el generador interactivo, según el siguiente resultado:

Como resultado, el archivo src\Foggyline\Checkout\Model\Guest\Cart.php se genera adicionalmente con el siguiente contenido:

<?php
namespace Foggyline\Checkout\Model\Guest;
class Cart
{
}

Si bien todos estos son ejemplos simples, demuestra que phpspec funciona en ambos sentidos:

  • Crear especificaciones basadas en clases concretas existentes
  • Generando clases concretas basadas en una especificación

Ejecutar nuestra prueba ahora debería darnos el siguiente resultado:

Ahora, fallemos deliberadamente en una prueba cambiando el método its_title_should_be_like() de spec\Foggyline\Catalog\Model\ProductSpec.php en la siguiente línea de código:

$this->getTitle()->shouldBeLike('Yellow');

Ejecutar la prueba ahora debería darnos el siguiente resultado:

Hay mucho más que decir sobre phpspec. Cosas como Stubs, Mocks, Spies, plantillas y extensiones enriquecen aún más nuestra experiencia de prueba de phpspec.

Comparte