Nach der Veröffentlichung von PHPUnit 8 haben einige Entwickler auf Twitter kundgetan, dass sie diese neue Version hassen. Zu einem gewissen Grad kann ich die angesprochenen Probleme verstehen und die Frustration der Entwickler nachvollziehen. Mit diesem Artikel möchte ich helfen, Frustration beim Aktualisieren von PHPUnit in Zukunft zu vermeiden.
PHPUnit 8 wurde am 1. Februar 2019 veröffentlicht. Wie in der Release Process-Dokumentation dargelegt, erscheint jedes Jahr eine neue Major-Version von PHPUnit am ersten Freitag im Februar.
Eine solche Major-Version bietet eine jährliche Gelegenheit zum Aufräumen. Neue Funktionalität wird meistens in Minor-Versionen, beispielsweise in PHPUnit 8.1 bis PHPUnit 8.5, die am jeweils ersten Freitag im April, Juni, August, Oktober und Dezember erscheinen, umgesetzt.
Abhängigkeit auf PHP 7.2
PHPUnit 8 benötigt PHP 7.2 (oder neuer). Die aktive Unterstützung von PHP 7.1 durch das PHP-Projekt endete am 1. Dezember 2018. Stand heute, im Februar 2019, werden nur PHP 7.2 und PHP 7.3 offiziell und aktiv unterstützt. PHPUnit 8 wird auf PHP 7.2 und PHP 7.3 unterstützt. Wenn Ende des Jahres PHP 7.4 erscheint, so wird auch diese Version von PHPUnit 8 unterstützt werden.
Wenn Sie noch PHP 7.1 einsetzen dann sollten Sie jetzt mit der Migration auf eine aktuelle PHP-Version, idealerweise PHP 7.3, beginnen. Das PHP-Projekt bietet keine Bugfixes mehr für PHP 7.1 und sicherheitskritische Fehler werden nur noch bis zum 1. Dezember 2019 behoben. Ein langfristiges Ziel sollte sein, das Aktualisieren der eingesetzten PHP-Version als regelmäßige Aufgabe – und nicht als spezielles Projekt, das nur alle Jubeljahre angegangen wird – zu begreifen. Der entsprechende Aktualisierungsprozess sollte sich am aktiven Support des PHP-Projekts für aktuelle PHP-Versionen ausrichten .
Wenn Sie PHP 7.2 noch nicht verwenden können oder wollen, dann können Sie natürlich auch PHPUnit 8 noch nicht verwenden. Noch stellt dies kein allzu großes Problem dar, da PHPUnit 7, das noch mit PHP 7.1 funktioniert, bis Februar 2020 mit Bugfixes unterstützt wird. Allerdings verpassen Sie natürlich sämtliche Verbesserungen neuerer Versionen von PHP und PHPUnit.
Rückgabetyp von Schablonenmethoden
Die Schablonenmethoden
von PHPUnit\Framework\TestCase
, setUpBeforeClass()
, setUp()
, assertPreConditions()
, assertPostConditions()
, tearDown()
, tearDownAfterClass()
, und onNotSuccessfulTest()
, haben nun einen deklarierten Rückgabetyp: void
. Implementierungen dieser Methoden müssen nun ebenfalls void
deklariert sein:
<?php declare ( strict_types = 1 ) ; |
use PHPUnit\Framework\TestCase ; |
final class MyTest extends TestCase |
{ |
protected function setUp ( ) |
{ |
} |
// ... |
} |
Versucht man die Tests der oben gezeigten Testklasse MyTest
mit PHPUnit 8 auszuführen so kommt es zu folgendem Compiler-Fehler:
$ ./tools/phpunit MyTest
PHP Fatal error:
Declaration of MyTest::setUp() must be compatible with
PHPUnit\Framework\TestCase::setUp(): void in ...
Sämtliche anderen Methoden von PHPUnit haben bereits seit PHPUnit 7 einen deklarierten void
Rückgabetyp, wo dies angebracht ist. Wegen des oben gezeigten Compiler-Fehlers wurde der void
Rückgabetyp für die Schablonenmethoden von PHPUnit\Framework\TestCase
nicht zur selben Zeit eingeführt, da dies einen Bruch der Abwärtskompatibilität ohne ausreichende Zeit zur Vorbereitung dargestellt hätte. Stattdessen enthielt die Release-Ankündigung für PHPUnit 7
einen Hinweis darauf, dass diese Methoden in PHPUnit 8 einen deklarierten void
Rückgabetyp bekommen würden. Darüber hinaus wurde folgende Empfehlung ausgesprochen:
Please declare your methods that overwrite the [template methods]
void
now so you are not affected by this backward compatibility break.
Mit dem void
Rückgabetyp drückt man aus, dass eine Methode keinen Wert zurückgibt. Wenn der Compiler eine Methode mit void
Rückgabetyp sowie einem return
im Methodenrumpf sieht, kommt es zu einem Fehler:
<?php declare ( strict_types = 1 ) ; |
final class Example |
{ |
public function doSomething ( ) : void |
{ |
return false ; |
} |
} |
Versucht man den oben gezeigten Code auszuführen so kommt es zu folgendem Compiler-Fehler:
$ php Example.php
PHP Fatal error: A void function must not return a value in ...
Dies ist nützlich, da es Programmierfehler offensichtlich macht.
Bevor die Schablonenmethoden von PHPUnit\Framework\TestCase
einen deklarierten void
Rückgabetyp hatten, konnten sie so implementiert werden, dass sie einen Wert zurückgaben. PHPUnit hat diesen Wert nie verwendet. Im Laufe der Jahre habe ich immer wieder Implementierungen von beispielsweise setUp()
gesehen, die einen Wert zurückgaben. Wo dies der Fall war, wurde setUp()
nicht nur von PHPUnit automatisch zum richtigen Zeitpunkt aufgerufen, sondern auch manuell in Testmethoden.
Dank des deklarierten void
Rückgabetyps ist die Tatsache, dass PHPUnit von Schablonenmethoden wie setUp()
keinen Rückgabewert erwartet, im Code expliziert. Diese Information kann sowohl von PHP selbst als auch von Werkzeugen zur statischen Code-Analyse genutzt werden. So oder so: diese Information ist wichtig und ermöglicht das frühe und einfache Finden von Programmierfehlern.
Man mag mit meiner Meinung, dass mehr Typinformationen zu Code führen, der sowohl von Menschen als auch von Werkzeugen einfacher und besser zu verstehen ist, nicht übereinstimmen. Weder ich noch PHPUnit schreiben Entwicklerinnen und Entwicklern vor, wie sie ihren Code zu schreiben haben. Ausschließlich an der Schnittstelle zwischen ihrem eigenen Code und dem von PHPUnit, beispielsweise wenn man von einer Klasse wie TestCase
ableitet und eine Methode wie setUp()
implementiert, muss man sich an die Vorgaben von PHPUnit halten. Tatsächlich gilt dies für nahezu sämtliche Third-Party Software, die man in seinem Projekt einsetzt. Frameworks propagieren Best Practices ebenso Werkzeuge wie Composer oder PHPUnit.
Ich glaube, dass die in PHP 7 eingeführten Verbesserungen des Typsystems wertvoll sind. Daher habe ich viel Arbeit und Zeit investiert, um die Codebasis von PHPUnit entsprechend zu modernisieren, beispielsweise durch die Einführung von deklarierten Rückgabetypen sowie der strikten Interpretation von skalaren Typen. Explizite Parametertypen und Rückgabetypen machen es jedem, der zur Entwicklung von PHPUnit beiträgt, einfacher, den Code zu verstehen. Je mehr Typinformationen im Code explizit sind, desto mehr Programmierfehler können verhindert werden. Allermindestens führen sie dazu, dass Fehler einfacher zu finden und zu beheben sind.
Abkündigungen
Software wächst mit der Zeit. Vor allem dann, wenn die Entwickler immer neue Funktionalität hinzufügen, ohne jemals alten Code zu entfernen. Es ist nicht klug, vor allem in einem Open Source-Projekt wo sämtliche Arbeit von Freiwilligen geleistet wird, wertvolle Entwicklungszeit auf die Wartung von Funktionalität zu verschwenden, die nicht mehr benötigt, selten genutzt oder problematisch ist. Daher entferne ich regelmäßig alte Funktionalität aus PHPUnit. Ich glaube, dass jedes Team von Entwicklern dies für ihre Projekte tun sollte. Aber das ist natürlich jedem Team selbst überlassen.
Wenn man seine Tests mit PHPUnit 8 ausführt, so kann es sein, dass man Warnungen für abgekündigte Funktionalität (Deprecation Warnings) sieht:
$ ./tools/phpunit MyTest
PHPUnit 8.0.2 by Sebastian Bergmann and contributors.
W 1 / 1 (100%)
Time: 33 ms, Memory: 4.00MB
There was 1 warning:
1) MyTest::testSomething
assertInternalType() is deprecated and will be removed in
PHPUnit 9. Refactor your test to use assertIsArray(),
assertIsBool(), assertIsFloat(), assertIsInt(),
assertIsNumeric(), assertIsObject(), assertIsResource(),
assertIsString(), assertIsScalar(), assertIsCallable(),
or assertIsIterable() instead.
WARNINGS!
Tests: 1, Assertions: 1, Warnings: 1.
Wenn man eine solche Warnung sieht, so ist dies kein Grund zur Panik.
Abgekündigte Zusicherungen wie assertInternalType()
im oben gezeigten Beispiel funktionieren weiterhin. Eine Deprecation Warning wird nur dann ausgegeben, wenn der Test ansonsten erfolgreich ist. Das bedeutet, dass eine Deprecation Warning keine Information über einen fehlgeschlagenen Test verbirgt.
PHPUnit geht anders als PHP vor, wenn es Warnungen zu abgekündigter Funktionalität ausgibt. Wird abgekündigte Funktionalität verwendet, so erzeugt PHP eine Nachricht vom Typ E_DEPRECATED
. Diese Nachricht wird allerdings nur dann angezeigt, wenn die Einstellung error_reporting
entsprechend konfiguriert ist. In meiner Erfahrung ist dies selten der Fall. In der Entwicklungsumgebung sollte error_reporting
auf -1
konfiguriert sein, damit man sämtliche Nachrichten bekommt. Es ist ebenfalls nicht zielführend, PHP so in der Entwicklungsumgebung zu konfigurieren, dass diese Nachrichten in ein Logfile geschrieben werden, das niemand anschaut. Dies sind wesentliche Gründe dafür, dass Entwickler überrascht sind, wenn eine neue Major-Version von PHP zuvor abgekündigte Funktionalität entfernt.
PHPUnit meldet einen Test, der erfolgreich war aber abgekündigte Funktionalität nutzt, mit einer Warnung. Eben weil ich weiß, wie Entwickler mit den E_DEPRECATED
Nachrichten von PHP umgehen, beziehungsweise nicht umgehen. Man kann den Warnungen für abgekündigte Funktionalität von PHPUnit nicht entgehen.
In der Standardkonfiguration führt eine Deprecation Warning nicht zu einem Shell-Exit-Code von 1
. Ein Continuous Integration-Server wird also ein Build nicht wegen der Verwendung von abgekündigter Funktionalität fehlschlagen lassen. Durch Konfiguration von failOnWarning="true"
in der phpunit.xml
kann man dieses Verhalten ändern und das Build fehlschlagen lassen, wenn ein Test abgekündigte Funktionalität von PHPUnit verwendet.
assertEquals()
Ohne Zweifel ist assertEquals()
die am häufigsten genutzte Zusicherungsmethode. Im Laufe der Jahre wurden ihrer API viele optionale Parameter hinzugefügt. Einige von diesen können nicht zusammen genutzt werden, was immer wieder zu Edge Case-Fehler in der zugrundeliegenden Implementierung geführt hat. Das Hauptproblem mit einer langen Liste von optionalen Parametern ist, dass man die ersten vier angeben muss, wenn man beispielsweise den fünften verwenden will. Und wer kann sich schon merken, wofür all diese Parameter gut sind? Ich jedenfalls nicht.
Um Abhilfe zu schaffen, wurden in PHPUnit 7.5 spezialisierte Alternativen
eingeführt. Daher sind in PHPUnit 8 die folgenden optionalen Parameter von assertEquals()
abgekündigt:
-
$delta
(stattdessenassertEqualsWithDelta()
benutzen) -
$maxDepth
(hatte schon lange keinen Effekt mehr) -
$canonicalize
(stattdessenassertEqualsCanonicalizing()
benutzen) -
$ignoreCase
(stattdessenassertEqualsIgnoringCase()
benutzen)
Diese Parameter werden in PHPUnit 9 entfernt.
Die Probleme mit assertEquals()
und seinen optionalen Parametern sind vergleichbar mit denen, die wir in der Vergangenheit mit getMock()
hatten. createMock()
, createPartialMock()
, etc. wurden eingeführt, um die verwirrende API von getMock()
mit sauberen, expliziten, separaten Methoden abzulösen.
Der Vollständigkeit halber sei erwähnt, dass alles, was oben ausgeführt wurde, auch für assertNotEquals()
, das Inverse zu assertEquals()
, gilt.
assertContains()
Im Laufe der Zeit wurden optionale Parameter auch zur assertContains()
Zusicherungsmethode hinzugefügt. Darüber hinaus wurde ihr Anwendungsbereich von Arrays sowie Objekten, die Iterator
implementieren, auf Strings ausgeweitet. Immer wieder kam es dabei zu Problemen, die sich auch der Umsetzung verschiedener, teilweise miteinander in Konflikt stehenden, Use Cases in derselben Codeeinheit ergaben.
Um diese Probleme zu beheben, wurden in PHPUnit 7.5 spezialisierte Alternativen
eingeführt, die nur auf Strings operieren. In PHPUnit 8 sind die optionalen Parameter $checkForObjectIdentity
, $checkForNonObjectIdentity
und $ignoreCase
abgekündigt. Darüber hinaus ist die Verwendung von assertContains()
mit String-Haystacks ebenfalls abgekündigt. Tests, die assertContains()
mit String-Haystacks verwenden, sollten umgeschrieben werden, sodass sie stattdessen assertStringContainsString()
verwenden.
Die genannten Parameter sowie die Möglichkeit, assertContains()
mit String-Haystacks zu verwenden, werden in PHPUnit 9 entfernt.
Beim Aufräumen von assertContains()
ist mir ein Fehler unterlaufen
, der es unmöglich gemacht, sicherzustellen, dass ein Objekt in einem iterable
enthalten ist, ohne dass bei den notwendigen Vergleichen der ===
Operator verwendet wird. Dieser Fehler wurde mit der Einführung von assertContainsEquals()
und assertNotContainsEquals()
in PHPUnit 8.0.2 behoben. Diese neuen Zusicherungen verwenden den ==
Operator anstelle des ===
Operators. Mein Dank gilt Rathes Sachchithananthan, der mich auf dieses Problem aufmerksam machte.
Der Vollständigkeit halber sei erwähnt, dass alles, was oben ausgeführt wurde, auch für assertNotContains()
, das Inverse zu assertContains()
, gilt.
assertInternalType()
Die Zusicherungsmethode assertInternalType()
kann benutzt werden, um sicherzustellen, dass eine Variable einen Wert von einem bestimmten Typ enthält, der nicht benutzerdefiniert ist. Hier ist ein Beispiel, das zeigt, wie man sicherstellen kann, dass eine Variable einen Wert vom Typ array
enthält:
$this->assertInternalType('array', $variable);
Ein wichtiger Aspekt der oben gezeigten Zusicherung ist in einem Parameter, 'array'
, versteckt. Das ist problematisch. Beispielsweise kann eine IDE keine automatische Vervollständigung für das Wort array
anbieten. Darüber hinaus gibt es keinen Schutz vor Tippfehlern im String 'array'
. Es ist ja schließlich nur ein String.
Um dieses Problem zu beheben, wurden in PHPUnit 7.5 spezialisierte Alternativen eingeführt. Diese neuen Zusicherungsmethoden verstecken keine wesentliche Information mehr in einem String-Parameter, sondern machen ihre Verantwortlichkeit explizit in ihrem Methodennamen:
$this->assertIsArray($variable);
Natürlich bedeutet eine explizitere API, dass es mehr Methoden gibt. Allerdings ist jede dieser Methoden einfacher, da sie nur genau einen Use Case implementiert. Dies führt zu Code, der einfacher zu verstehen ist. Er ist darüber hinaus einfacher zu schreiben, da die IDE nun automatische Vervollständigung bieten kann.
Der Vollständigkeit halber sei erwähnt, dass alles, was oben ausgeführt wurde, auch für assertNotInternalType()
, das Inverse zu assertInternalType()
, gilt.
assertArraySubset()
Die Methode assertArraySubset()
war eine konstante Quelle von Verwirrung und Frustration, was man hier
, hier
, hier
, hier
, hier
, hier
oder hier
nachlesen kann.
Diese Situation ist entstanden, weil ich den ursprünglichen Pull Request nicht gründlich genug geprüft habe. Ich selbst hatte keine Verwendung für die vorgeschlagene Zusicherung. Auf den ersten Blick sah die Implementierung so aus, als würde sie den Use Case des Entwicklers, der die Zusicherung vorschlug, erfüllen.
Im Laufe der Jahre habe ich immer wieder Pull Requests akzeptiert, von denen ich dachte, dass sie nur Fehler in der Implementierung von assertArraySubset()
beheben. Allerdings fügten einige dieser Pull Request neue Funktionalität hinzu, die teilweise im Konflikt zu anderen Anwendungsfällen von assertArraySubset()
stand. Dies hätte nicht passieren dürfen. Allerdings kann diese zusätzliche Funktionalität auch nicht mehr entfernt werden, ohne dass es zu einem Bruch der Abwärtskompatibilität kommt.
Während der Arbeit an PHPUnit 8 bin ich zu der Erkenntnis gelangt, dass die Probleme von assertArraySubset()
nicht behoben werden können, ohne die Abwärtskompatibilität zu brechen. Ich habe daher beschlossen, assertArraySubset()
abzukündigen und die Methode in PHPUnit 9 zu entfernen. Dies führt zu einem Bruch der Abwärtskompatibilität, der explizit und offensichtlich ist. Jegliche Änderung am Verhalten von assertArraySubset()
wäre auch ein Bruch der Abwärtskompatibilität, allerdings wäre dieser dann implizit und nicht offensichtlich.
Wer diese Funktionalität nützlich findet, ist herzlich eingeladen, den entsprechenden Code aus PHPUnit herauszulösen und in einem separaten Projekt als Erweiterung für PHPUnit weiterzupflegen.
Nicht öffentliche Eigenschaften
Zu große Objekte mit problematischen Abhängigkeiten sind in Legacy Code üblich. Solche Objekte müssen häufig indirekt getestet werden, indem man ihren nicht öffentlichen Zustand betrachtet. PHPUnit bietet hierfür eine Reihe von Zusicherungen, die auf nicht öffentlichen Eigenschaften arbeiten
wie beispielsweise assertAttributeEquals()
. Diese Zusicherungen wurden ausschließlich für das Testen von Legacy Code eingeführt. Ihre Verwendung war nie als Best Practice, schon gar nicht für neuen Code, empfohlen.
Leider wurden diese Zusicherungen allzu oft missbraucht, um neuen Code zu testen, bei dem man die Testbarkeit vernachlässigt hat. Es ist ein Fehler, den Inhalt von nicht öffentlichen Eigenschaften testen zu wollen. Anstatt den Zustand eines Objektes testen zu wollen, sollte vielmehr das Verhalten eines Objekts getestet werden.
Es hat sich im Laufe der Jahre gezeigt, dass die Bereitstellung von Zusicherungen wie assertAttributeEquals()
zu schlechten Testpraktiken geführt hat. Ich habe mich daher entschlossen, diese Zusicherungen in PHPUnit 8 abzukündigen und in PHPUnit 9 zu entfernen.
Nicht öffentliche Methoden dürfen nicht direkt getestet werden, indem man ihre Sichtbarkeit unter Zuhilfenahme der Reflection API umgeht. Der private Zustand eines Objekts darf ebenfalls nicht in einem Test betrachtet werden. Diese beiden Praktiken führen zu einer engen Kopplung von Testcode an den getesteten Code und damit dazu, dass man Implementierungsdetails anstelle der API testet. Sobald sich die Implementierung ändert, geht der Test kaputt, da er sich auf private Implementierungsdetails verlässt.
Testen von Ausnahmen
Vor drei Jahren schrieb ich bereits über Best Practices für das Testen von Ausnahmen
. Damals empfahl ich die Verwendung von $this->expectException(MyException::class);
anstelle der bis dahin üblichen Annotation @expectedException MyException
an der Testmethode. Meinen Gedankengang hinter dieser Empfehleung habe ich im Detail im verlinkten Artikel dargelegt. Hier ist die Kurzfassung:
Einerseits hat PHP keine nativen Annotationen. Stattdessen werden Annotation in Code-Kommentaren verwendet. In einem Code-Kommentar kann man keine unqualifizierten Klassennamen, beispielsweise Example
anstatt vendor\project\Example
, verwenden, wie man dies im Code tun könnte, nachdem man die Klasse in den aktuellen Namespace importiert hat.
Andererseits bleibt PHPUnit nicht anderes übrig, als einen Test als erfolgreich zu bewerten, wenn zu irgendeinem Zeitpunkt seiner Ausführung die über die Annotation angegebene Ausnahme ausgelöst wird. Dies ist nicht immer das, was man will.
Ich glaube heute, dass die Verwendung von Annotationen für das Erwarten von Ausnahmen mehr schadet, als dass sie nutzt. Daher ist die Verwendung von Annotationen wie @expectedException
in PHPUnit 8 abgekündigt. Die entsprechende Funktionalität wird in PHPUnit 9 entfernt.
Upgrade auf PHPUnit 8
PHPUnit setzt auf Semantic Versioning . Kurz und knapp bedeutet dies, dass man sich auf die drei folgenden Regeln verlassen kann, wenn man sich die Versionsnummern von PHPUnit anschaut:
- Die Major-Version wird angehoben, wenn es inkompatible Änderungen, beispielsweise an der öffentlichen API, gibt
- Die Minor-Version wird angehoben, wenn neue Funktionalität ohne inkompatible Änderungen hinzugefügt wird
- Die Patch-Version wird angehoben, wenn Fehler ohne inkompatible Änderungen behoben werden
Wo wir gerade beim Thema Semantic Versioning sind: vor einiger Zeit habe ich in einem Artikel beschrieben, wie man die Abhängigkeit auf PHPUnit richtig verwaltet, wenn man in seinem Projekt Composer für die Verwaltung von Abhängigkeiten verwendet.
Ein Werkzeug wie PHPUnit darf nicht auf Gut glück und ohne Plan auf eine neue Major-Version aktualisiert werden. Schließlich installiert man auch keine neue Major-Version von PHP auf seinem Produktivserver, ohne dass man die Liste der Änderungen liest. Oder erwartet man, dass man seinen Code ohne jede Änderung mit einer neuen Major-Version der Programmiersprache ausführen kann? Dasselbe gilt für das Framework, auf dem eine Anwendung aufbaut ebenso wie für die Komponenten, die man in seinem Projekt verwendet.
Wenn ich einen Arbeitstag nach der Veröffentlichung von PHPUnit 8 Beschwerden darüber lese, dass existierende Tests ohne Änderungen am Testcode nicht mit der neuen Version ausgeführt werden können, dann muss ich davon ausgehen, dass diese Entwickler das Upgrade ungeplant durchgeführt haben.
Man darf kein Upgrade auf eine neue Major-Version von PHPUnit durchführen, ohne dass man sich darüber informiert, welche Inkompatibilitäten es gibt oder welche Funktionalität abgekündigt wurde. Man darf mit der Migration eines Projektes auf eine neue PHPUnit-Version nicht beginnen, wenn nicht alle Tests mit der aktuellen Version erfolgreich und ohne Fehler ausgeführt werden können.
Man sollte die Tests, zunächst nur lokal und nicht in der kontinuierlichen Integration, mit der neuen PHPUnit-Version ausführen. Hierbei achtet man auf die Warnungen, insbesondere die Warnungen für abgekündigte Funktionalität. Danach sollte man die Release-Ankündigung lesen und lernen, welche Änderungen am eigenen Testcode vorgenommen werden müssen. Die entsprechenden Änderungen sollte man exemplarisch für jeweils einen Vertreter jeder Kategorie durchführen. Danach sollte man einschätzen können, wieviel Arbeit notwendig ist, um die notwendigen Änderungen für die gesamte Testsuite durchzuführen.
Viele dieser Änderungen können automatisiert durchgeführt werden. php-cs-fixer ist das Standardwerkzeug in der PHP-Welt, um schnell und einfach Änderungen in einer gesamten Codebasis durchzuführen.
So kann php-cs-fixer beispielsweise jeder Methode, die kein return
Statement enthält, automatisch die entsprechende Deklaration eines void
Rückgabetyps hinzufügen. Nehmen wir einmal an, wie hätten den folgenden Code in einer tests/MyTest.php
Quelltextdatei:
<?php declare ( strict_types = 1 ) ; |
use PHPUnit\Framework\TestCase ; |
class MyTest extends TestCase |
{ |
protected function setUp ( ) |
{ |
} |
} |
Den fehlenden void
Rückgabetyp können wir mithilfe von php-cs-fixer einfach ergänzen:
$ php-cs-fixer fix --allow-risky=yes --rules void_return tests
Loaded config default.
Using cache file ".php_cs.cache".
1) tests/MyTest.php
Fixed all files in 0.003 seconds, 10.000 MB memory used
tests/MyTest.php
sieht nun so aus:
<?php declare ( strict_types = 1 ) ; |
use PHPUnit\Framework\TestCase ; |
class MyTest extends TestCase |
{ |
protected function setUp ( ) : void |
{ |
} |
} |
php-cs-fixer wird in der Lage sein, beispielsweise assertInternalType('array', $variable)
durch assertIsArray($variable)
zu ersetzen. Die Arbeit der Entwickler an Unterstützung für die Migration von Testcode für PHPUnit kann hier
, hier
und hier
verfolgt werden.
Rector
ist ebenfalls einen Blick wert. Mit diesem Werkzeug kann man beispielsweise Aufrufe von assertEquals()
, welche die abgekündigten optionalen Parameter verwenden, migrieren
.
Fazit
Die Ursache für die Frustration der Entwickler, die ihren Unmut über PHPUnit ausgedrückt haben, liegt nicht in technischen Änderungen der neuen Major-Version, sondern vielmehr im Fehlen eines sinnvollen Prozesses für das Aktualisieren von Abhängigkeiten in einem Projekt.
Die Tatsache, dass Entwickler ihren setUp()
Methoden den void
Rückgabetyp hinzufügen müssen, wurde ein Jahr vor der Veröffentlichung von PHPUnit 8 angekündigt
. Die notwendigen Änderungen hätten zu jedem Zeitpunkt zwischen der Veröffentlichung von PHPUnit 7 und der Veröffentlichung von PHPUnit 8 durchgeführt werden können. Dies hätte keine Probleme verursacht und wäre ohne großen Aufwand möglich gewesen. Insbesondere wenn ein Werkzeug wie php-cs-fixer verwendet wird, das die Änderungen schnell und komfortabel durchführt.
Die Release-Ankündigung von PHPUnit 8 enthält ähnliche Informationen für PHPUnit 9, das im Februar 2020 erscheinen wird. Allerdings habe ich manchmal den Eindruck, dass niemand solche Ankündigungen liest.
Die Wartung eines Softwareprojekts darf nicht nur reaktiv sein. Sie muss vielmehr proaktiv sein in dem Sinne, dass die Entwickler sich Änderungen bewusst sind, auf die sie in den nächsten zwölf Monaten reagieren müssen, wenn sie sich unschöne Überraschungen beim Upgrade auf eine neue Major-Version von Programmiersprache, Framework, Komponenten oder Werkzeugen ersparen wollen.
Allermindestens bedeutet dies, dass man sorgfältig die Release-Ankündigungen neuer Major-Versionen von Third-Party Software, die man einsetzt, durchliest und die entsprechenden notwendigen Änderungen in einen der nächsten Sprints einplant. Idealerweise verfolgt man die Entwicklung solcher Third-Party Software in den entsprechenden Upstream-Projekten. So lernt man nicht nur frühzeitig, was zukünftige Versionen an Änderungen bringen, sondern kann sich im Bedarfsfall in Diskussionen einbringen.