带有 Mock 示例测试的 PHPSpec 始终 returns null 但实现按预期工作

PHPSpec with Mock example test always returns null but implementation works as expected

我想从 PHPSpec 开始,所以我正在使用两个简单的 类。第一个负责将百分比缩小或放大到数字,第二个负责使用百分比应用程序(如 Mock)计算产品价格。

PercentageToNumberApplyerSpec(规范):

namespace spec\My;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class PercentageToNumberApplyerSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType('My\PercentageToNumberApplyer');
    }

    function it_enlarges_a_number_with_a_given_percentage()
    {
        $this->enlarge(100, 20)->shouldReturn(120);
        $this->enlarge(80, 25)->shouldReturn(100);
        $this->enlarge(20, 50)->shouldReturn(30);
    }

    function it_reduces_a_number_with_a_given_percentage()
    {
        $this->reduce(100, 20)->shouldReturn(80);
        $this->reduce(80, 10)->shouldReturn(72);
        $this->reduce(250, 20)->shouldReturn(200);
    }
}

PercentageToNumberApplyer(实施):

<?php

namespace My;

class PercentageToNumberApplyer
{
    /**
     * Enlarge given number with a given percentage
     *
     * @param $number
     * @param $percentage
     * @return float
     */
    public function enlarge($number, $percentage)
    {
        return $this->calculate($number, $percentage) + $number;
    }

    /**
     * Reduce given number with a given percentage
     *
     * @param $number
     * @param $percentage
     * @return mixed
     */
    public function reduce($number, $percentage)
    {
        return $number - $this->calculate($number, $percentage);
    }

    /**
     * @param $number
     * @param $percentage
     * @return float
     */
    private function calculate($number, $percentage)
    {
        return $number * $percentage / 100;
    }
}

PriceCalculatorSpec(规范):

<?php

namespace spec\My;

use My\PercentageToNumberApplyer;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class PriceCalculatorSpec extends ObjectBehavior
{
    function let(PercentageToNumberApplyer $percentageToNumberApplyer)
    {
        $this->beConstructedWith($percentageToNumberApplyer);
    }

    function it_calculates_price_discount($percentageToNumberApplyer)
    {
        $number = 100;
        $discount = 20;

        $percentageToNumberApplyer->reduce($number, $discount)->shouldBeCalled();

        $this->applyDiscountTo($number, $discount)->shouldReturn(80);
    }
}

问题

问题是在上面的例子中 运行ning phpspec 运行 结果是:

- it calculates price discount
expected [integer:80], but got null

价格计算器(实现):

<?php

namespace My;

class PriceCalculator
{
    /**
     * @var PercentageToNumberApplyer
     */
    private $percentageToNumberApplyer;

    /**
     * @param PercentageToNumberApplyer $percentageToNumberApplyer
     */
    public function __construct(PercentageToNumberApplyer $percentageToNumberApplyer)
    {
        $this->percentageToNumberApplyer = $percentageToNumberApplyer;
    }

    /**
     * @param $basePrice
     * @param $discount
     * @return mixed
     */
    public function applyDiscountTo($basePrice, $discount)
    {
        return $this->percentageToNumberApplyer->reduce($basePrice, $discount);
    }
}

为什么即使测试失败,以下用例仍然有效:

$priceCalculator = new \My\PriceCalculator(new \My\PercentageToNumberApplyer());

$price = $priceCalculator->applyDiscountTo(100, 20);

$price 价值 80...

您的情况不需要模拟。存根就可以了。在 PHP Test doubles patterns with prophecy.

中阅读更多关于测试替身的信息

而不是模拟调用:

$percentageToNumberApplyer->reduce($number, $discount)->shouldBeCalled();

存根:

$percentageToNumberApplyer->reduce($number, $discount)->willReturn(80);

接下来,你只需要期望计算出的实际上是returned:

$percentageToNumberApplyer->reduce($number, $discount)->willReturn(80);

$this->applyDiscountTo($number, $discount)->shouldReturn(80);

这是因为您不应该关心是否已拨打电话。你只对结果感兴趣。

根据经验,您通常会:

  • 执行操作的模拟协作者(命令方法)
  • stub 合作者 return 某事(查询方法)

大多数时候最好将两者分开(Command/Query分开),我们不需要同时模拟和存根。