2014-09-05 76 views
5

我很高興地編寫單元測試,但他們碰撞時,我將它們都運行在一起。我正在測試此課程: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中僞造尚未重構一些全局函數。

我沒有自動裝載機,不想處理隔離EVERY測試,因爲它需要永遠。我嘗試在測試1 & 2的文檔塊中插入組合@runTestsInSeparateProcesses@preserveGlobalState enabled /禁用,我仍然得到相同的錯誤。

+0

您需要提供一個可重複的例子。 – hek2mgl 2014-09-09 21:47:02

+0

你爲什麼通過'require_once'語句設置你的模擬,而不是使用Mock Builder API使用適當的模擬功能?閱讀:https://phpunit.de/manual/current/en/test-doubles。html#test-doubles.mock-objects – 2014-09-09 21:53:24

+1

@ hek2mgl,我希望這個例子更好。 – PeerBr 2014-09-09 22:09:50

回答

5

要理解這種行爲,您需要看看PHPUnit的工作原理。 getMockBuilder()->getMock(),動態地創建模擬類下面的代碼:

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的將前MyClassTest執行InjectTest,這意味着模擬生成器將定義僞類和MyClass已被定義時MyClassTest::setUpBeforeClass將被調用。這就是錯誤的原因。我希望我能解釋。否則,深入研究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()); 
    } 
} 
+0

謝謝。你對「虛擬」聲明的解釋有很多幫助。我假設所有的php測試文件都包含在執行第一個測試之前? – PeerBr 2014-09-10 01:13:53

+0

@PeerBr哦,是的,所有php測試文件都包含在第一個測試執行之前,這就是爲什麼虛擬類聲明不會發生(因爲MyClassTest上的require_once已經被執行)。這很重要,錯過了說... – hek2mgl 2014-09-10 08:01:13