Mock Objekte in PHPUnit

Mock bedeutet auf Deutsch soviel wie Fälschung oder Attrappe. In PHPUnit werden Mock Objekte dazu genutzt um das Verhalten anderer Objekte zu simulieren.

Das Ziel eines Unit Tests ist es, einen Teil(Unit) zu testen. Eine solche Unit ist eine Klasse. Hat eine Klasse Abhängigkeiten zu anderen Klassen oder gar zu externen Diensten, sollte man für diese abhängigen Objekte Mock Objekte nutzen.

Im Beispiel auf der vorherigen Seite wurde ein Testcase für die PersonCollection erstellt. Im Grunde wollen wir darin „nur“ die Funktionalität dieser Klasse testen, nicht aber der Klasse „Person“ selbst. Aus diesem Grund baue ich den Testcase nun so um, das durch Mock Objekte nicht mehr die Funktionalität der Klasse Person getestet wird:

<?php

require_once '../Classes/Person.php';
require_once '../Classes/PersonCollection.php';

class PersonCollectionTest extends PHPUnit_Framework_TestCase {
	protected $collection;

	/**
	* @return void
	*/
	public function setUp() {
		$this->collection = new PersonCollection();
	}

	/**
	* @param string $lastName
	* @return Person
	*/
	protected function getPersonMock($lastName) {
		$mock = $this->getMock('Person',array('getLastName'),array(),'',false);
		$mock->expects($this->exactly(2))
				->method('getLastName')
				->will($this->returnValue($lastName));

		return $mock;
	}

	/**
	* @test
	*/
	public function testGetLastNameStartingWith() {
		$karl	= $this->getPersonMock('Meyer');
		$willi	        = $this->getPersonMock('Schneider');
		$peter 	= $this->getPersonMock('Müller');
		$frida	= $this->getPersonMock('Salomon');
		$fred	= $this->getPersonMock('Schmidt');

		$this->collection->add($karl)->add($willi)->add($peter);
		$this->collection->add($frida)->add($fred);

		$schItems = $this->collection->getLastNameStartingWith('Sch');
		$schCount = $schItems->count();

		$errorMsg = 'Unexpected itemcount starting with Sch';
		$this->assertEquals(2,$schCount,$errorMsg);

		$sItems   = $this->collection->getLastNameStartingWith('S');
		$sCount   = $sItems->count();
		$errorMsg = 'Unexpected itemcount starting with s';

		$this->assertEquals(3,$sCount,$errorMsg);
	}
}

?>

Mocks mit PHPUnit erzeugen und Verhalten definieren

Zunächst möchte ich hier auf die einzelnen Schritte bei der Definition des Mockobjektes eingehen:

Statt mittels „new Person“ ein Person Objekt zu erzeugen wird in der Methode „getPersonMock“ ein Mock Objekt erzeugt.


Da in dieser Methode viel neues passiert, wollen wir sie genauer analysieren:

protected function getPersonMock($lastName) {
	$mock = $this->getMock('Person',array('getLastName'),array(),'',false);
	$mock->expects($this->exactly(2))
			->method('getLastName')
			->will($this->returnValue($lastName));

	return $mock;
}

Erzeugen mittels getMock

Zunächst wird mittels „$this->getMock“ ein neues Mock Objekt erzeugt. Die Methode getMock hat folgende Parameter:

  1. Der Name der zu Mockenden Klasse, also der original Klasse.

  2. Ein array mit den Methodennamen, die überschrieben werden sollen. Die Logik ist hier wie folgt. Der Standardwert ist ein leerer Array, sollte dies der Fall sein werden alle Methoden gemockt. Sobald man eine oder mehrere Methodennamen übergibt werden diese gemockt. Diese vorgehensweise hörrt sich zunächst unlogisch an, ist sie aber nicht, da bei einem Mock Objekt oft alle Methoden gemockt werden sollen und dann alle Methoden der Klasse aufgezählt werden müssten.

    Technisch gesehen funktioniert „mocking“ so, dass zur Laufzeit eine erbende Klasse erzeugt wird. Deshalb ist zu beachten, dass nur „public“ und „protected“ Methoden gemockt werden können.

  3. Das dritte Argument sind die übergebenen Konstuktor Argumente. Da ich die Funktionaliät der Methode „getLastName“ überschreiben möchte und die Initialisierung mittels Kontruktor in diesem Moment keinen Sinn mehr macht übergeben ich keine Argumente und verhindere dass der original Konstruktor aufgerufen wird.

  4. Mit diesem Parameter kann man den Klassennamen des Mockobjektes angeben. Habe ich bislang noch nicht benötigt.

  5. Dieses Argument dient dazu zu verhindern dass der Konstruktor der original Klasse aufgerufen wird.

Erwartung und Verhalten der gemockten Methode definieren

Bis zu diesem Schritt wurde das Mock Objekt erzeugt, aber wir haben noch nicht definiert, was die „gefälschte“ Methode „getLastName“ tun soll.

$mock->expects($this->exactly(2))
            ->method('getLastName')
            ->will($this(>returnValue($lastName));

Die Methode „$this->expects()“ dient dazu zu Formulieren, wie oft wir erwarten dass die Methode aufgerufen wird. Wenn diese Erwartung nicht einträgt schlägt der Testfall fehl. „$this->expects()“ kann also auch dazu genutzt werden um sicherzustellen, dass eine Methode in einer bestimmten Häufigkeit aufgerufen wird.


Das Arguments von „$this->expects()“ sind ebenfalls Rückgaben von PHPUnit Methode. Folgende habe ich schon verwerden:

$this->once(), $this->exactly(2), $this->any()

Sollte sich der Code häufig ändern, und die Menge der Aufrufe nicht relevant sein, empfiehlt sich der Einsatz von „$this->any()“ da sonst der Testfall oft unrechtmässig fehlschlagen würde.

Methodenname

Mittels der Methode „method“ wird der Name der gemockten Methode übergeben.

Rückgabewert und Verhalten mittels "will" definieren

Die Methode „will“ dient dazu, das Verhalten der gemockten Methode zu definieren. Das Argument ist ebenfalls das Ergebnis einer PHPUnit Methode.

Folgende habe ich schon verwenden:


$this->returnValue“ gibt einen genauen Wert zurück. Dies kann eine beliebige Variable aus dem Testfall sein, also auch ein andere MockObjekt. Im oberen Testfall wurde es dazu genutzt den Nachnamen zurückzugeben.

„$this->returnCallback“ gibt einen Callback zurück. Einen solchen Callback braucht man immer dann, wenn man verhalten im gemockten Objekt simulieren möchte.

Ein Callback kann eine öffentliche Methode im Testfall sein:

->will($this->returnCallback(array('PersonCollectionTestcase','myCallback')));

oder man verwendet die mit PHP 5.3 eingeführten Closures und übergibt direkt eine Funktion:

->will($this->returnCallback(function(){ 
 ...
});

Parameter mittels with prüfen

Mit der Methode „with“ ist es darüberhinaus möglich Erwartungen an den übergebenen Parameter zu formulieren. Dies habe ich hier nicht genutzt, da es nicht benötigt wurde.

Navigation