【问题标题】:PHPSpec with Mock example test always returns null but implementation works as expected带有 Mock 示例测试的 PHPSpec 始终返回 null 但实现按预期工作
【发布时间】:2015-05-19 14:30:43
【问题描述】:

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

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);
    }
}

问题

问题是在上面的例子中运行 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 unit-testing mocking phpspec


    【解决方案1】:

    您的情况不需要模拟。存根会做。在PHP Test doubles patterns with prophecy 阅读更多关于测试替身的信息。

    而不是模拟调用:

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

    存根:

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

    接下来,您只需要期望实际返回的是计算的内容:

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

    这是因为您不应该关心是否有呼叫。你只对结果感兴趣。

    根据经验,您通常会:

    • 模拟执行操作的协作者(命令方法)
    • 返回某些东西的存根协作者(查询方法)

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

    【讨论】:

    • 再次感谢 Jakub :),但如果我这样做: $percentageToNumberApplyer->reduce(100, 20)->willReturn(100) 这不是真的测试通过,所以每次我都必须想想写“真实”对吗?
    • 从这个规范的角度来看,你在这里放什么并不重要。目的是协作者返回的任何内容都会在此处返回。实际进行计算的类是自己指定的。如果计算中存在错误并且您没有嘲笑进行计算的合作者,那么您将不得不测试失败。如果您嘲笑合作者,唯一失败的测试将是您实际验证计算的测试。这是一个更好的反馈。
    • 感谢您的澄清,这是有道理的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    • 2011-04-21
    • 2014-03-22
    • 2016-08-27
    • 1970-01-01
    相关资源
    最近更新 更多