PHPUnit: включить класс после издевательства над ним

Я счастливо пишу модульные тесты, но они сталкиваются, когда я запускаю их все вместе. Я тестирую этот класс:

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 отлично работает сам по себе. Но 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()); } } 

Я использую bootstrap.php для подделки некоторых глобальных функций, которые еще не реорганизованы.

У меня нет автозагрузчиков, и я не хочу обрабатывать-изолировать КАЖДЫЙ тест, потому что он берет навсегда. Я попытался вставить комбинации @runTestsInSeparateProcesses и @preserveGlobalState enabled / отключенные в докблоках обоих тестов 1 и 2, я все равно получаю ту же ошибку.

Чтобы понять это поведение, вам нужно посмотреть, как работает 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 перед MyClassTest , это означает, что макет-строитель будет определять класс фиктивного класса, а MyClass уже определен, когда MyClassTest::setUpBeforeClass будет вызван. Вот почему ошибка. Надеюсь, я мог бы объяснить. В противном случае выкопайте код PHPUnit.


Решение:

Отбросьте метод setUpBeforeClass() и поместите инструкцию require_once поверх тестов. setUpBeforeClass() не предназначен для включения классов. Обратитесь к документам .

Btw, имеющий 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()); } }