【问题标题】:Testing that no methods (whatever the name) will be invoked in PHPUnit?测试在 PHPUnit 中不会调用任何方法(无论名称如何)?
【发布时间】:2019-01-03 21:54:55
【问题描述】:

部分测试对象:

class AddOptionsProviderArgumentPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if(!$container->hasDefinition('gremo_highcharts')) {
            return;
        }

        if(!$container->hasParameter('gremo_highcharts.options_provider')) {
            return;
        }

        // ...
    }
}

我想断言:

  • hasDefinition() 使用参数 'gremo_highcharts' 调用将返回 false
  • 方法process()返回,即不会调用其他方法

一种解决方案是断言对hasParameter() 的后续调用:

public function testProcessWillReturnIfThereIsNoServiceDefinition()
{
    $container = $this->getMockedContainerBuilder();
    $pass = new AddOptionsProviderArgumentPass();

    $container->expects($this->once())
        ->method('hasDefinition')
        ->with($this->equalTo('gremo_highcharts'))
        ->will($this->returnValue(false));

    // Expects that hasParameter() is never invoked
    $container->expects($this->never())
        ->method('hasParameter');

    $pass->process($container);
}

但这似乎不是一个优雅的解决方案。

【问题讨论】:

    标签: php unit-testing phpunit


    【解决方案1】:

    要表达any method,可以使用$this->anything()

    完整示例:

    <?php
    
    class fooTest extends PHPUnit_Framework_TestCase {
        public function testNeverCallNothing() {
            $mock = $this->getMock('mockMe');
            $mock->expects($this->never())->method($this->anything());
            //$mock->bar();
        }
    }
    
    class mockMe {
        public function bar() {}
    }
    

    输出:

    PHPUnit 3.7.10-4-ga0bccf3 by Sebastian Bergmann.
    
    .
    
    Time: 0 seconds, Memory: 6.50Mb
    
    OK (1 test, 1 assertion)
    

    在方法调用中注释时

    $mock->bar();
    

    然后输出:

    PHPUnit 3.7.10-4-ga0bccf3 by Sebastian Bergmann.
    
    F
    
    Time: 0 seconds, Memory: 6.50Mb
    
    There was 1 failure:
    
    1) fooTest::testNeverCallNothing
    mockMe::bar() was not expected to be called.
    
    .../tests/neverCallMe/fooTest.php:9
    
    FAILURES!
    Tests: 1, Assertions: 0, Failures: 1.
    

    只允许调用一个方法,不允许调用其他方法

    这看起来有点难看,但也有效

    <?php
    
    class fooTest extends PHPUnit_Framework_TestCase {
    
        public function testNeverCallNothing() {
            $mock = $this->getMock('mockMe');
            $mock->expects($this->once())->method('foo');
            $mock->expects($this->never())->method(
                $this->logicalNot($this->matches('foo'))
            );
            $mock->foo();
            //$mock->bar();
        }
    
    
    }
    
    class mockMe {
        public function bar() {}
        public function foo() {}
    }
    

    有效。在其他方法调用中注释时,它会像上面一样失败。

    如果想要允许调用多个方法,它会变得更加冗长:

    $this->logicalNot(
        $this->logicalOr(
            $this->matches('foo'),
            $this->matches('baz'),
            $this->matches('buz')
        )
    )
    

    【讨论】:

    【解决方案2】:

    这是个例外吗?如果是这样,您可以将 first return(为什么还要返回 void?)更改为 抛出特定异常。然后使用 PHPUnit 来验证是否确实捕获了该特定异常。

    编辑: 同样使用 Phake,您可以在测试结束时编写类似这样的内容:(类似于使用 PHPUnit Mock Objects 调用 ->never())

    Phake::verify($container, Phake::times(0))->hasParameter();
    

    这在存根方法调用和验证方法(是否已存根)已被调用之间产生了区别。

    【讨论】:

    • 我返回 void 是因为不应进行进一步处理,并且永远不会使用返回值。我不能抛出任何异常,不是意外行为...
    • 对,我明白了。那么也许你可以解释为什么这个解决方案对你来说并不优雅?就像我说的,您可以尝试包含 Phake 并将存根 (hasDefinition) 与验证 (hasParameter) 分开。
    【解决方案3】:

    在测试此类方法时,请尝试查看大局。不要下降到ifs 和returns 的水平,要更高。通过断言在return 之后没有进行其他调用,您真正测试的是PHP 的本机语句,而不是您的方法的逻辑。这就像你不信任returns。相信我的话,在return 语句之后,该方法中没有执行任何操作:)

    相反,测试你的方法的逻辑!

    逻辑是什么?

    好吧,根据你的代码,你有这个类 AddOptionsProviderArgumentPass 和它的 process 方法。 pocess 方法采用 ContainerBuilder 并以某种方式对其进行处理。所以,您需要测试的是process 方法是否能很好地完成它的工作。您在方法中的ifs 表示为了成功处理ContainerBuilder 而需要满足的一些约束。

    您如何了解process 是否成功?

    按其返回类型。

    如果什么都不返回怎么办?

    检查它的副作用。你对ContainerBuilder做了哪些事情。

    所以,这就是我的看法。

    /**
     * @test
     */
    public function shouldNotProcessWithoutHighcharts()
    {
        // Arrange
        $container = $this->buildContainer();
        $container->removeDefinition('gremo_highcharts');
        $pass = new AddOptionsProviderArgumentPass();
    
        // Act
        $pass->process($container);
    
        // Assert
        $this->assertFalse($container->hasWhatYouNeedItToHaveAfterProcessing())
    }
    
    /**
     * @test
     */
    public function shouldNotProcessWithoutHighchartsOptionsProvider()
    {
        // Arrange
        $container = $this->buildContainer();
        $container->getParameterBag()->remove('gremo_highcharts.options_provider');
        $pass = new AddOptionsProviderArgumentPass();
    
        // Act
        $pass->process($container);
    
        // Assert
        $this->assertFalse($container->hasWhatYouNeedItToHaveAfterProcessing())
    }
    
    private function buildContainer()
    {
        $container = new ContainerBuilder();
        $container->setParameter('gremo_highcharts.options_provider');
        $container->setDefinition('gremo_highcharts');
        return $container;
    }
    

    最后一点

    不要依赖ifs的顺序,它可以改变!

    【讨论】:

      【解决方案4】:

      也许会创建 Phake 模拟并调用

      Phake::verifyNoInteraction($mock);
      

      是解决这个问题的好方法。这是手册的链接:https://phake.readthedocs.io/en/2.1/method-verification.html#verifying-no-interaction-with-a-mock-so-far

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-28
        • 1970-01-01
        • 2015-01-20
        • 2017-03-05
        相关资源
        最近更新 更多