Я счастливо пишу модульные тесты, но они сталкиваются, когда я запускаю их все вместе. Я тестирую этот класс:
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()); } }