Saltar a contenido

Segundo escenario

Vamos a implementar ahora el segundo escenario

Escenario: Pagar con dinero y puntos
Dado que he comprado 5 menús del número 1
Cuando pido la cuenta recibo una factura de 55 euros
Y pago con 10 puntos y 54 euros
Entonces la factura está pagada
Y he obtenido 0 puntos

Si intentamos ejecutar la prueba directamente vemos que hay ya muchos pasos que pasan:

vendor/bin/behat features/menu.feature:23
Característica: Pagar un menú
Reglas:

- 1 punto por cada euro.
- 10 puntos equivalen a un descuento de 1 euros.
- El IVA es del 10%

Antecedentes: # features/menu.feature:9
Dados los siguientes menús: # FeatureContext::losSiguientesMenus()
| número | precio |
| 1 | 10 |
| 2 | 12 |
| 3 | 8 |

Escenario: Pagar con dinero y puntos # features/menu.feature:23
Dado que he comprado 5 menús del número 1 # FeatureContext::queHeCompradoMenusDelNumero()
Cuando pido la cuenta recibo una factura de 55 euros # FeatureContext::pidoLaCuentaReciboUnaFacturaDeEuros()
Y pago con 10 puntos y 54 euros # FeatureContext::pagoConPuntosYEuros()
TODO: write pending definition
Entonces la factura está pagada # FeatureContext::laFacturaEstaPagada()
Y he obtenido 0 puntos # FeatureContext::heObtenidoPuntos()

1 scenario (1 pending)
6 steps (3 passed, 1 pending, 2 skipped)
0m0.02s (9.98Mb)

Solo tenemos que implementar el pago con puntos.

Describir la funcionalidad

El primer paso, es escribir la descripción en nuestra especificación de la clase Bill:

<?php

namespace spec\Restaurant;

use Restaurant\Bill;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Restaurant\Priced;

class BillSpec extends ObjectBehavior
{
    // ...

    function it_can_be_paith_with_money_and_points_and_get_no_points(Priced $item)
    {
        $this->addItem($item);
        $this->payWithMoney(1000);
        $this->payWithPoints(10);
        $this->restToPay()->shouldBe(0);
        $this->getPoints()->shouldBe(0);
    }
}

De nuevo, ejecutamos las pruebas de phpspec para que se generen los métodos necesarios en nuestra clase Bill y refactorizamos nuestro código para pasar la prueba:

<?php
declare(strict_types=1);

namespace Restaurant;

class Bill
{
    const VAT = '1.10';
    private $items;
    private $amount;
    private $points;

    public function __construct()
    {
        $this->items = [];
        $this->points = 0;
        $this->amount = 0;
    }

    public function getTotal(): int
    {
        return (int) round($this->totalWithoutVAT() * self::VAT);
    }

    public function addItem(Priced $item): void
    {
        $this->items[] = $item;
    }

    public function payWithMoney(int $amount): void
    {
        $this->amount = $amount;
    }

    public function restToPay(): int
    {
        return $this->getTotal() - $this->amount - $this->getMoneyPoints();
    }

    public function getPoints(): int
    {
        if ($this->points > 0) {
            return 0;
        }

        return (int) floor($this->totalWithoutVAT() / 100);
    }

    private function totalWithoutVAT(): int
    {
        return array_reduce($this->items, function ($carry, Priced $priced) {
            return $carry + $priced->price();
        }, 0);
    }

    public function payWithPoints(int $points): void
    {
        $this->points = $points;
    }

    private function getMoneyPoints(): int
    {
        return 100 * ($this->points / 10);
    }
}

Comprobamos que pasamos las pruebas y pasamos a implementar la historia de usuario.

Completando la historia de usuario

Creamos el código que implementa el paso que nos falta:

<?php

use Behat\Behat\Context\Context;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;

/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context
{
    // ...

    /**
    * @When pago con :points puntos y :money euros
    */
    public function pagoConPuntosYEuros($points, $money)
    {
        $this->bill->payWithMoney($money * 100);
        $this->bill->payWithPoints($points);
    }
}

Y ya hemos conseguido terminar otro escenario:

vendor/bin/behat features/menu.feature:23
Característica: Pagar un menú
  Reglas:

  - 1 punto por cada euro.
  - 10 puntos equivalen a un descuento de 1 euros.
  - El IVA es del 10%

  Antecedentes:                 # features/menu.feature:9
    Dados los siguientes menús: # FeatureContext::losSiguientesMenus()
      | número | precio |
      | 1      | 10     |
      | 2      | 12     |
      | 3      | 8      |

  Escenario: Pagar con dinero y puntos                   # features/menu.feature:23
    Dado que he comprado 5 menús del número 1            #     FeatureContext::queHeCompradoMenusDelNumero()
    Cuando pido la cuenta recibo una factura de 55 euros #     FeatureContext::pidoLaCuentaReciboUnaFacturaDeEuros()
    Y pago con 10 puntos y 54 euros                      #     FeatureContext::pagoConPuntosYEuros()
    Entonces la factura está pagada                      #     FeatureContext::laFacturaEstaPagada()
    Y he obtenido 0 puntos                               #     FeatureContext::heObtenidoPuntos()

1 scenario (1 passed)
6 steps (6 passed)
0m0.02s (10.09Mb)

Conclusiones

Como vamos viendo, el código que se genera en phpspec es realmente el código que implementa nuestras reglas de negocio. En behat solo implementamos código para poder usar el dominio en las pruebas y comprobar que nuestras dos clases (Menu y Bill) trabajan bien juntas.