PHPUnit:模拟后包含类 [英] PHPUnit: include class after mocking it

查看:75
本文介绍了PHPUnit:模拟后包含类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在愉快地编写单元测试,但是当我一起运行它们时它们会发生冲突.我正在测试这堂课:

I'm happily writing unit tests, but they clash when I run them all together. I'm testing this class:

class MyClass {

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

使用此测试.所有测试都具有如下结构:

using this test. All tests have a structure like this:

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将崩溃,因为我重新声明了该类.

MyClassTest runs fine on its own. But PHPUnit will crash because I redeclare the class, if another test mocks MyClass and happens to run first:

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伪造了一些尚未重构的全局函数.

I do use a bootstrap.php to fake some global functions not yet refactored.

我没有自动加载程序,也不想对每个测试进行过程隔离,因为它会花费很多时间.我尝试将组合@runTestsInSeparateProcesses@preserveGlobalState enabled/disabled插入到Test 1& 2,我仍然遇到相同的错误.

I have no auto loaders and don't want to process-isolate EVERY test, because it takes forever. I tried inserting combinations @runTestsInSeparateProcesses and @preserveGlobalState enabled/disabled in the docblocks of both Test 1 & 2, I still get the same error.

推荐答案

要了解此行为,您需要了解PHPUnit的工作方式. getMockBuilder()->getMock(),为模拟类动态创建以下代码:

To understand this behaviour, you need to have a look at how PHPUnit works. getMockBuilder()->getMock(), dynamically creates the following code for the mock class:

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,它将添加以下伪声明:

If MyClass hasn't already been loaded at this time, it adds the following dummy declaration:

class MyClass
{
}

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

This code will then getting parsed using eval() (!).

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

Since PHPUnit will execute InjectTest before MyClassTest, this means that the mock builder will define the dummy class and MyClass is already defined when MyClassTest::setUpBeforeClass will get called. That's why the error. I hope I could explain. Otherwise, dig into PHPUnit's code.

解决方案:

拖放setUpBeforeClass()方法并将require_once语句放在测试之上. setUpBeforeClass()并不意味着包含类.请参考文档.

Drop the setUpBeforeClass() method and put the require_once statement on top of the tests. setUpBeforeClass() is not meant for including classes. Refer to the docs.

顺便说一句,将require_once放在最前面将起作用,因为PHPUnit将在开始第一个测试之前之前包含每个测试文件.

Btw, having require_once on top will work because PHPUnit will include every test file before starting the first test.

tests/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());
    }
}

tests/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());
    }   
}

这篇关于PHPUnit:模拟后包含类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆