Einführung von Unit-Tests (trotz "hard dependencies")
Ich habe mich die letzten Tage ein wenig mit den Möglichkeiten von Unit-Tests beschäftigt.
Bei Unit-Tests war bisher ja immer das Problem, dass die Klassen so "hart" miteinander "verdrahtet" sind, dass man keine Klasse für sich selbst testen kann.
Beispiel Benutzer erstellen: Schreibt in die Datenbank, löst Events aus, (schreibt in die Logs), verschickt Emails etc.
Damit man unabhängig von diesen Abhängigkeiten wird, nutzt man sog. "Mocks", die diese Klassen simulieren/ersetzen.
In einem Unit-Test definiert man dann über den Mock welche Methodenaufrufe erwartet werden und was für Werte diese zurückgeben sollen.
Hier habe ich einen Beispiel-Test erstellt: https://github.com/JanWennrich/PHP-Sorting-Algorithms/blob/hard-dependency-mocking/tests/PhpSorting/SorterTest.php#L16-L22
Ich erstelle einen Mock für die Klasse bzw. das Interface SortingAlgorithm
.
Dann definiere ich, dass die Methode sort
meines Mocks den Wert [1, 2, 3]
zurückgeben soll.
Da ich Dependency Injection verwende, kann ich meiner zu testenden Klasse nun per new Sorter($sortingAlgorithmStub)
meinen Mock als Abhängigkeit übergeben.
Statt einer konkreten Klasse, die das SortingAlgorithm
-Interface implementiert, verwendet die zu testende Klasse nun meinen Mock.
Wenn man Dependency Injection verwendet und somit eine lose Kopplung der Klassen hat, kann man die Mocks beim Testen also ganz einfach injecten und damit verwenden.
In den QUIQQER Paketen wird leider keine Dependency Injection verwendet (sollte evtl. geändert werden, siehe #1257).
Somit können wir der zu testenden Klasse nicht einfach unser Mock-Objekt als Ersatz für eine Abhängigkeit übergeben.
Mit dem Mock-Framework Mockery
, lässt sich dieses Architektur-Problem aber umgehen.
Das Framework ermöglicht es, bestehende Klassen über den Autoloader zu überschreiben.
So ist es möglich Mocks selbst dann zu verwenden, wenn keine Dependency Injection möglich ist - also "hard dependencies" vorliegen.
Als Beispiel habe ich hier die Klasse TestSort
, die per new InsertionSort()
eine "hard dependency" erstellt.
Wenn man die Klasse jetzt testen möchte, wird also zwangsweise auch die Klasse InsertionSort
instanziiert und verwendet, was unerwünschte Seiteneffekte haben könnte.
Daher muss die InsertionSort
Klasse gemockt werden.
In Mockery gibt es dazu die overload:
-Anweisung beim Erstellen eines Mocks.
Hier findet sich das dazugehörige Beispiel: https://github.com/JanWennrich/PHP-Sorting-Algorithms/blob/hard-dependency-mocking/tests/PhpSorting/Algorithm/TestSortTest.php#L20-L26
Wenn im Beispiel nun $testSort->sort([])
aufgerufen wird, wird nicht die InsertionSort
-Klasse erstellt sondern das Mock-Objekt verwendet.
Mit dem Verfahren ist es also möglich richtige Unit-Tests für QUIQQER und seine Pakete zu schreiben.
Jede Klasse kann so als eigenständige "Unit" ohne die ganzen Abhängigkeiten getestet werden.
Sieht jemand etwas, das gegen dieses Vorgehen spricht?
PS: Das Verfahren funktioniert zwar, aber ist dabei langsamer als normale Unit-Tests und ist auch nur ein Workaround für eine "schlechte" Code-Architektur.
Wenn wir neuen Code auf Basis von Dependency Injection schreiben, fällt das Testen noch leichter (siehe dazu #1257).