Gelegentlich verwenden die zu testenden Methoden native PHP-Funktionen wie file_get_contents()
, die unerwünschte Nebeneffekte erzeugen. Wie wir darum herum kommen, könnt ihr im folgenden lesen.
Schlecht testbarer Code
Zunächst ein Beispiel des Problems.
1 2 3 4 5 6 7 8 9 |
class TheDoer { public function doIt($url) { $data = file_get_contents($url); return (empty($data) ? null : $data); } } |
Hier wird die native PHP-Funktion file_get_contents()
verwendet. Insbesondere wenn Daten von einem externen Server geladen werden, muss im Testsystem dieser Aufruf unterbunden werden.
Ein Test wie der folgende wäre also gefährlich.
1 2 3 4 5 6 7 |
class TheDoerTest extends \PHPUnit_Framework_TestCase { public function testDoIt() { $class = new TheDoer(); $this->assertEquals('', $class->doIt('http://xyz.example.com')); } } |
Lösung: Native PHP-Funktionen kapseln
Die Lösung ist denkbar einfach. Wir erstellen eine Fassade für derartige Funktionen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * Class FileOperator * This is a facade for PHP file operations. * * @package Bvv\Library */ class FileOperator { /** * Load a file with file_get_contents. * * @param string $fileName * * @return string */ public function get($fileName) { return file_get_contents($fileName); } /** * Save to file with file_put_contents. * * @param string $fileName * @param string $data * * @return int */ public function put($fileName, $data) { return file_put_contents($fileName, $data); } } |
Diese kleine Helferklasse landet wie so vieles andere auch im Dependency Injektor und wird bei Bedarf statt der ursprünglichen Methode verwendet.
Der Programmcode nach dem Umbau
Unsere Klasse sieht dann also so aus:
1 2 3 4 5 6 7 8 9 10 11 |
class TheDoer { public function doIt($url) { /** @var FileOperator $fo */ $fo = DI::get('fileOperator'); $data = $fo->get($url); return (empty($data) ? null : $data); } } |
Der Test nach dem Umbau
Der Test kann jetzt einfach im Dependency Injektor ein Mock-Objekt von FileOperator ablegen und damit verhindern, dass tatsächlich eine URL abgefragt wird.
1 2 3 4 5 6 7 8 9 |
public function testDoIt() { $fo = $this->getMock('FileOperator'); $fo->expects($this->once())->method('get')->with('http://xyz.example.com')->will($this->returnValue('some data')); DI::set('fileOperator', $fo); $class = new TheDoer(); $this->assertEquals('some data', $class->doIt('http://xyz.example.com')); } |