2010-03-01 14 views
38

ich nach dem besten Weg suchen über die Prüfung der folgenden statischen Methode zu gehen (genauer gesagt eine Lehre Modell verwenden):PHPUnit Mock-Objekte und statische Methoden

class Model_User extends Doctrine_Record 
{ 
    public static function create($userData) 
    { 
     $newUser = new self(); 
     $newUser->fromArray($userData); 
     $newUser->save(); 
    } 
} 

Idealerweise würde ich ein Mock-Objekt verwenden, um sicherzustellen, dass "fromArray" (mit den gelieferten Benutzerdaten) und "save" wurden aufgerufen, aber das ist nicht möglich, da die Methode statisch ist.

Irgendwelche Vorschläge?

Antwort

42

Sebastian Bergmann, der Autor von PHPUnit, hatte kürzlich einen Blogeintrag über Stubbing and Mocking Static Methods. Mit PHPUnit 3.5 und PHP 5.3 sowie konsequente Nutzung der späten statische Bindung, können Sie

$class::staticExpects($this->any()) 
     ->method('helper') 
     ->will($this->returnValue('bar')); 

-Update tun:staticExpectsdeprecated as of PHPUnit 3.8 und wird vollständig mit späteren Versionen entfernt werden.

+11

Bemerkenswert " Dieser Ansatz funktioniert nur für das Stubben und Mocking von statischen Methodenaufrufen, bei denen Aufrufer und Aufrufer derselben Klasse angehören, weil [statische Methoden sind Tod zur Testbarkeit] (http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/). " –

+1

Die Funktion 'staticExpects' wurde ab PHPUnit v4 entfernt. In [this thread on github] (https://github.com/sebastianbergmann/phpunit-mock-objects/issues/137) finden Sie eine Erklärung warum. –

+4

Da wir wissen, dass 'staticExpects' aus der aktuellen Version von PHPUnit entfernt wurde, was ist der alternative Weg dies ohne 'staticExpects' zu erreichen? –

0

Das Testen von statischen Methoden wird im Allgemeinen als ein bisschen hart angesehen (wie Sie wahrscheinlich schon bemerkt haben), besonders vor PHP 5.3.

Können Sie Ihren Code nicht ändern, um keine statische Methode zu verwenden? Ich sehe nicht wirklich, warum Sie hier eine statische Methode verwenden. das könnte wahrscheinlich zu etwas nicht-statischem Code umgeschrieben werden, nicht wahr?


Zum Beispiel könnte so etwas wie diese nicht den Trick:

class Model_User extends Doctrine_Record 
{ 
    public function saveFromArray($userData) 
    { 
     $this->fromArray($userData); 
     $this->save(); 
    } 
} 

nicht sicher, was Sie testen sein werden; aber zumindest keine statische Methode mehr ...

+0

Danke für den Vorschlag, es ist mehr Stil als alles andere. Ich könnte die Methode in diesem bestimmten Fall nicht statisch machen (obwohl ich es vorziehen würde, sie ohne Instantiierung zu verwenden). –

+11

Die Frage ist definitiv, statische Methoden zu verspotten - dem Autor zu sagen, "keine statischen Methoden zu verwenden", schneidet den Senf nicht ab. – Lotus

10

Es gibt jetzt die AspectMock Bibliothek mit diesem helfen:

https://github.com/Codeception/AspectMock

$this->assertEquals('users', UserModel::tableName()); 
$userModel = test::double('UserModel', ['tableName' => 'my_users']); 
$this->assertEquals('my_users', UserModel::tableName()); 
$userModel->verifyInvoked('tableName'); 
+7

Diese Bibliothek ist Gold! Aber ich denke, sie sollten einen Disclaimer auf ihre Seite setzen: "Nur weil Sie globale Funktionen und statische Methoden mit unserer Bibliothek testen können, heißt das nicht, dass Sie auf diese Weise neuen Code schreiben sollten." Ich habe irgendwo gelesen, dass ein schlechter Test besser ist als überhaupt keine Tests, und mit dieser Bibliothek können Sie Ihrem Legacy-Code ein Sicherheitsnetz hinzufügen. Stellen Sie sicher, dass Sie neuen Code besser schreiben :) – pedromanoel

0

Ein anderer möglicher Ansatz ist mit der Moka Bibliothek:

$modelClass = Moka::mockClass('Model_User', [ 
    'fromArray' => null, 
    'save' => null 
]); 

$modelClass::create('DATA'); 
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]); 
$this->assertEquals(1, sizeof($modelClass::$moka->report('save'))); 
1

Ich würde eine neue Klasse in der Einheit Test Namespace, die den Model_User erweitert und testen, dass. Hier ein Beispiel:

Original-Klasse:

class Model_User extends Doctrine_Record 
{ 
    public static function create($userData) 
    { 
     $newUser = new self(); 
     $newUser->fromArray($userData); 
     $newUser->save(); 
    } 
} 

Mock Klasse rufen in Unit-Test (s):

use \Model_User 
class Mock_Model_User extends Model_User 
{ 
    /** \PHPUnit\Framework\TestCase */ 
    public static $test; 

    // This class inherits all the original classes functions. 
    // However, you can override the methods and use the $test property 
    // to perform some assertions. 
} 

In Ihrem Unit-Test:

use Module_User; 
use PHPUnit\Framework\TestCase; 

class Model_UserTest extends TestCase 
{ 
    function testCanInitialize() 
    { 
     $userDataFixture = []; // Made an assumption user data would be an array. 
     $sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing. 

     $sut::test = $this; // This is just here to show possibilities. 

     $this->assertInstanceOf(Model_User::class, $sut); 
    } 
} 
+0

Ich benutze diese Methode, wenn ich keine zusätzliche PHP-Bibliothek für mich verwenden möchte. – b01