【问题标题】:PHPUnit: include class after mocking itPHPUnit:模拟后包含类
【发布时间】:2014-09-05 19:50:57
【问题描述】:

我很乐意编写单元测试,但是当我一起运行它们时它们会发生冲突。我正在测试这个类:

class MyClass {

    public function sayHello() {
        return 'Hello world';
    }
}

使用此测试。所有测试都有这样的结构:

class MyClassTest extends PHPUnit_Framework_TestCase {
    private $subject;

    public static function setUpBeforeClass() {
        require_once('path/to/MyClass.php');
    }

    public function setUp() {
        $this->subject = new MyClass();
    }

    public function testSayHello() {
        $this->assertEquals('Hello world', $this->subject->sayHello());
    }
}

MyClassTest 自己运行良好。但是如果另一个测试模拟 MyClass 并且碰巧首先运行,PHPUnit 会崩溃,因为我重新声明了该类

class Inject {

    private $dependency;

    public function __construct(MyClass $myClass) {
        $this->dependency = $myClass;
    }

    public function printGreetings() {
        return $this->dependency->sayHello();
    }
}

class InjectTest extends PHPUnit_Framework_TestCase {

    public function testPrintGreetings() {
        $myClassMock = $this
            ->getMockBuilder('MyClass')
            ->setMethods(array('sayHello'))
            ->getMock();
        $myClassMock
            ->expects($this->once())
            ->method('sayHello')
            ->will($this->returnValue(TRUE));

        $subject = new Inject($myClassMock);

        $this->assertEquals('Hello world', $subject->printGreetings());
    }
}

我确实使用 bootstrap.php 来伪造一些尚未重构的全局函数。

我没有自动加载程序,也不想处理隔离每个测试,因为这需要很长时间。我尝试在测试 1 和 2 的文档块中插入组合 @runTestsInSeparateProcesses@preserveGlobalState enabled/disabled,我仍然得到相同的错误。

【问题讨论】:

  • 你需要提供一个可重现的例子。
  • 您为什么要通过require_once 语句设置您的模拟,而不是使用Mock Builder API 使用适当的模拟功能?阅读:phpunit.de/manual/current/en/…
  • @hek2mgl,希望这个例子更好。
  • @MikeBrant,require_once 包括正在测试的主题,而不是模拟(使用 API 构建)。我已经更新了问题。

标签: php unit-testing phpunit


【解决方案1】:

要了解这种行为,您需要了解 PHPUnit 的工作原理。 getMockBuilder()->getMock(),为 mock 类动态创建如下代码:

class Mock_MyClass_2568ab4c extends MyClass implements PHPUnit_Framework_MockObject_MockObject
{
    private static $__phpunit_staticInvocationMocker;
    private $__phpunit_invocationMocker;

    public function __clone()
    {
        $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationMocker();
    }

    public function sayHello()
    {
        $arguments = array();
        $count     = func_num_args();

        if ($count > 0) {
            $_arguments = func_get_ ... 

     # more lines follow ...

如果此时 MyClass 尚未加载,则添加以下虚拟声明:

class MyClass
{
}

然后将使用 eval() (!) 解析此代码。

由于 PHPUnit 将执行 InjectTest before MyClassTest,这意味着模拟生成器将定义虚拟类,并且在调用 MyClassTest::setUpBeforeClass 时已经定义了 MyClass。这就是错误的原因。我希望我能解释一下。否则,请深入研究 PHPUnit 的代码。


解决方案:

删除setUpBeforeClass() 方法并将require_once 语句放在测试之上。 setUpBeforeClass() 并不意味着包含类。请参阅docs

顺便说一句,将require_once 放在顶部会起作用,因为 PHPUnit 将包含每个测试文件之前开始第一次测试。

测试/MyClassTest.php

require_once __DIR__ . '/../src/MyClass.php';

class MyClassTest extends PHPUnit_Framework_TestCase {
    private $subject;

    public function setUp() {
        $this->subject = new MyClass();
    }

    public function testSayHello() {
        $this->assertEquals('Hello world', $this->subject->sayHello());
    }
}

测试/InjectTest.php

require_once __DIR__ . '/../src/Inject.php';

class InjectTest extends PHPUnit_Framework_TestCase {

    public function testPrintGreetings() {
        $myClassMock = $this
            ->getMockBuilder('MyClass')
            ->setMethods(array('sayHello'))
            ->getMock();
        $myClassMock
            ->expects($this->once())
            ->method('sayHello')
            ->will($this->returnValue(TRUE));

        $subject = new Inject($myClassMock);

        $this->assertEquals(TRUE, $subject->printGreetings());
    }   
}

【讨论】:

  • 谢谢。您对dummy 声明的解释帮助很大。我假设在执行第一个测试之前包含所有 php 测试文件?
  • @PeerBr 哦,是的,所有 php 测试文件都包含在第一个测试执行之前,这就是为什么虚拟类声明不会发生(因为 MyClassTest 顶部的 require_once 已经执行)。这很重要,不想说...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
  • 2017-04-16
  • 1970-01-01
  • 2020-11-23
  • 2016-01-05
  • 2015-03-23
  • 2014-04-03
相关资源
最近更新 更多