PHPUnit:在模拟之后包含类

Pee*_*rBr 5 php phpunit unit-testing

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

class MyClass {

    public function sayHello() {
        return 'Hello world';
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这个测试.所有测试都有这样的结构:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

MyClassTest自己运行得很好.但PHPUnit会崩溃,因为我重新启动了类,如果另一个测试模拟MyClass并且碰巧先运行:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我没有自动加载器,也不想处理 - 隔离每个测试,因为它需要永远.我尝试在测试1和2的docblock中插入组合@runTestsInSeparateProcesses@preserveGlobalState enabled/禁用,我仍然得到相同的错误.

hek*_*mgl 5

要了解此行为,您需要了解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 ...
Run Code Online (Sandbox Code Playgroud)

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

class MyClass
{
}
Run Code Online (Sandbox Code Playgroud)

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

由于PHPUnit InjectTest 之前 将执行MyClassTest,这意味着模拟构建器将定义虚拟类,并且MyClassMyClassTest::setUpBeforeClass被调用时已经定义.这就是错误的原因.我希望我能解释一下.否则,深入了解PHPUnit的代码.


解:

删除setUpBeforeClass()方法并将require_once语句放在测试之上.setUpBeforeClass()不适合包括课程.请参阅文档.

顺便说一句,拥有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());
    }
}
Run Code Online (Sandbox Code Playgroud)

测试/ 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());
    }   
}
Run Code Online (Sandbox Code Playgroud)