Auf dem Bild sitzt ein Mann im Anzug im Freien an einem Tisch vor einem Laptop. Er hat beide Hände frustriert in die Luft gestreckt und blickt nach oben. Sein Gesichtsausdruck wirkt verzweifelt oder genervt. Die Szene spielt auf einer leeren Straße mit Bänken und Gebäuden im Hintergrund. Vor ihm steht außerdem ein Coffeebecher auf dem Tisch.

Tests auf Knopfdruck auszuführen ist eine großartige Sache. Das Test Framework ebenfalls auf Knopfdruck zu aktualisieren klingt vielversprechend. Oder vielleicht doch nicht?

Nach der Veröffentlichung von PHPUnit 8 haben einige Entwicklerinnen und Entwickler in sozialen Medien ihrer Abneigung gegenüber bestimmten Änderungen in dieser neuen Version Ausdruck verliehen. Zum Teil verstehe ich diese Probleme und kann die Frustration nachvollziehen. Dieser Artikel bietet Orientierung, wie du solche Frustration in Zukunft vermeiden kannst.

Eine neue Major Version von PHPUnit wie PHPUnit 8 wird jedes Jahr am ersten Freitag im Februar veröffentlicht. Eine neue Major Version bietet jährlich die Möglichkeit für Aufräumarbeiten. Die meisten neuen Funktionen werden in Minor Versions implementiert, PHPUnit 8.1 bis PHPUnit 8.5, die jeweils am ersten Freitag im April, Juni, August, Oktober und Dezember fällig sind.

Erforderliche PHP-Version

PHPUnit 8 erfordert PHP 7.2 (oder neuer). Dies liegt daran, dass der aktive Support für PHP 7.1 durch das PHP-Projekt am 1. Dezember 2018 endete. Zum heutigen Zeitpunkt sind die einzigen aktiv unterstützten PHP-Versionen PHP 7.2 und PHP 7.3. PHPUnit 8 wird auf PHP 7.2 und PHP 7.3 unterstützt und wird auch auf PHP 7.4 unterstützt werden.

Wenn du noch PHP 7.1 verwendest, solltest du damit beginnen, deine Software auf eine unterstützte PHP-Version zu migrieren, idealerweise PHP 7.3, da der Sicherheitssupport für PHP 7.1 durch das PHP-Projekt am 1. Dezember 2019 enden wird. Als langfristiges Ziel musst du PHP-Upgrades zu einem Teil deiner normalen Betriebsabläufe machen und den Upgrade-Zyklus deines PHP-Stacks mit dem Release-Zyklus des PHP-Projekts abstimmen.

Wenn du PHP 7.2 noch nicht verwenden kannst, kannst du natürlich auch PHPUnit 8 nicht sofort verwenden. Da PHPUnit 7 bis zum 7. Februar 2020 unterstützt wird, gibt es kein unmittelbares Problem. Du kannst vorerst bei PHP 7.1 und PHPUnit 7 bleiben, verpasst aber natürlich alle Verbesserungen in PHP 7.3 und PHPUnit 8.

Rückgabetyp von Template-Methoden

Die Template-Methoden von TestCase, setUpBeforeClass(), setUp(), assertPreConditions(), assertPostConditions(), tearDown(), tearDownAfterClass() und onNotSuccessfulTest(), haben jetzt eine void-Rückgabetypdeklaration. Implementierungen dieser Methoden müssen nun ebenfalls als void deklariert werden, andernfalls erhältst du einen Compilerfehler:

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class MyTest extends TestCase
{
    protected function setUp()
    {
    }

    // ...
}

Der Versuch, die Tests der oben gezeigten MyTest-Testfallklasse mit PHPUnit 8 auszuführen, führt zu dem unten gezeigten Compilerfehler:

❯ ./tools/phpunit MyTest
PHP Fatal error:
Declaration of MyTest::setUp() must be compatible with
TestCase::setUp(): void in ...

Alle anderen Methoden von PHPUnit, die keinen Wert zurückgeben, haben seit PHPUnit 7 eine void-Rückgabetypdeklaration. Aufgrund des oben genannten Compilerfehlers wurde die void-Rückgabetypdeklaration in PHPUnit 7 nicht zu den Template-Methoden von TestCase hinzugefügt, da dies zu einem Bruch der Rückwärtskompatibilität geführt hätte, ohne Entwicklerinnen und Entwickler genug Zeit zur Vorbereitung zu geben. Stattdessen enthielt die Release-Ankündigung für PHPUnit 7 die Information, dass diese Änderung ein Jahr später in PHPUnit 8 erfolgen würde. Zusätzlich wurde folgender Hinweis gegeben:

Bitte deklariere deine Methoden, die die [Template-Methoden] überschreiben, jetzt als void, damit du von diesem Bruch der Rückwärtskompatibilität nicht betroffen bist.

Der void-Rückgabetyp wird verwendet, um auszudrücken, dass eine Methode nichts zurückgibt. Wenn der Compiler eine Methode sieht, die eine void-Rückgabetypdeklaration hat und die Methode einen Wert zurückgibt, gibt der Compiler einen Fehler aus:

<?php declare(strict_types=1);
final class Example
{
    public function doSomething(): void
    {
        return false;
    }
}

Der Versuch, den oben gezeigten Code auszuführen, führt zu dem unten gezeigten Compilerfehler:

❯ php Example.php
PHP Fatal error: A void function must not return a value in ...

Dies ist nützlich, da es Programmierfehler sichtbar machen kann.

Bevor sie eine void-Rückgabetypdeklaration hatten, konnten die Template-Methoden von TestCase so implementiert werden, dass sie einen Wert zurückgaben. PHPUnit hat diesen Wert nie verwendet. Im Laufe der Jahre habe ich jedoch Implementierungen von setUp() gesehen, die einen Rückgabewert hatten. In diesen Fällen wurde die setUp()-Methode nicht nur automatisch von PHPUnit vor der Ausführung jeder Testmethode der betreffenden Testfallklasse aufgerufen, sondern auch manuell durch Code in Testmethoden.

Dank der void-Rückgabetypdeklaration wird nun explizit im Code deutlich, dass PHPUnit keinen Rückgabewert von einer Template-Methode erwartet. Diese Information wird sowohl vom PHP-Compiler und der Laufzeitumgebung als auch von statischen Analysetools genutzt. In jedem Fall sind diese Typinformationen entscheidend, um Probleme so früh wie möglich zu finden.

Du stimmst vielleicht nicht mit meiner Meinung überein, dass mehr Typinformationen zu Code führen, der sowohl für Menschen als auch für statische Analysetools leichter zu verstehen ist. Ich werde nicht vorschreiben, wie du deinen Code schreiben sollst, und PHPUnit auch nicht. Nur an der Schnittstelle zwischen PHPUnit-Code und deinem Code, zum Beispiel wenn du eine Klasse wie TestCase erweiterst und eine Template-Methode wie setUp() implementierst, musst du PHPUnits Vorgaben folgen. Tatsächlich fördern die meisten Drittanbieter-Softwarepakete, die du in deinen Projekten verwendest, Best Practices. Frameworks tun das, ebenso wie Tools wie Composer oder PHPUnit.

Ich glaube, dass es sehr wertvoll ist, die Verbesserungen am Typsystem in PHP 7 zu nutzen. Deshalb habe ich viel Zeit und Mühe aufgewendet, PHPUnits Codebasis zu modernisieren, um die strikte Interpretation von Skalar-Typdeklarationen und Rückgabetypdeklarationen besser zu nutzen. Explizite Parameter- und Rückgabetypen machen diesen Code auch für alle, die zu PHPUnit beitragen, viel leichter verständlich. Dank mehr Typinformationen können einige Codierfehler verhindert oder zumindest viel leichter debuggt und behoben werden.

Deprecations

Software wächst im Laufe der Zeit, besonders wenn Maintainer:innen weiterhin neue Funktionen hinzufügen, ohne jemals Code zu entfernen. Es ist nicht klug, besonders in einem Open-Source-Projekt, in dem alle Arbeit von Ehrenamtlichen geleistet wird, wertvolle Entwicklungszeit mit der Pflege von Funktionen zu verschwenden, die nicht mehr benötigt werden, selten verwendet werden oder problematisch sind. Deshalb entferne ich regelmäßig alte Funktionalität aus PHPUnit. Ich glaube, dass dein Projekt dasselbe tun sollte, aber das liegt natürlich ganz bei dir.

Es ist möglich, dass du Deprecation-Warnungen siehst, wenn du deine Tests mit PHPUnit 8 ausführst:

❯ ./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.

Falls du solche Deprecation-Warnungen siehst: kein Grund zur Panik.

Veraltete Assertions funktionieren weiterhin. Eine Warnung wird nur für Tests generiert, die eine veraltete Assertion verwenden und die sonst als erfolgreich gemeldet würden. Das bedeutet, dass eine Deprecation-Warnung keine Informationen über einen Testfehler verbirgt.

PHPUnit verhält sich anders als PHP in der Art und Weise, wie es die Verwendung veralteter Funktionalität meldet. Wenn eine veraltete PHP-Funktion verwendet wird, wird eine Nachricht vom Typ E_DEPRECATED erstellt. Diese Nachricht wird jedoch nur angezeigt, wenn die Entwicklerinnen und Entwickler PHP's error_reporting-Einstellung entsprechend konfiguriert haben. Nach meiner Erfahrung haben Entwicklerinnen und Entwickler entweder error_reporting nicht korrekt konfiguriert (es sollte für die Entwicklung auf -1 gesetzt werden, damit alle Fehlermeldungen angezeigt werden) oder schauen sich Fehlermeldungen nie an, die in irgendeiner Logdatei vergraben sind. Deshalb sind manche Entwicklerinnen und Entwickler überrascht, dass ihre Software nicht mehr funktioniert, wenn eine neue Major Version von PHP veröffentlicht wird, die zuvor veraltete Funktionalität entfernt.

PHPUnit meldet einen Test, der veraltete Funktionalität verwendet, mit einer Warnung, weil ich weiß, wie Entwicklerinnen und Entwickler PHP's E_DEPRECATED-Nachrichten verwenden – oder besser gesagt, nicht verwenden. Du kannst diese Information von PHPUnit nicht abwählen.

Standardmäßig beendet PHPUnits Kommandozeilen-Testrunner mit Shell-Exit-Code 0, wenn die Verwendung einer veralteten Funktion gemeldet wird. Dieser Shell-Exit-Code wird verwendet, um anzuzeigen, dass kein Fehler aufgetreten ist. Diese Information wird beispielsweise von Continuous-Integration-Umgebungen genutzt, um zu entscheiden, ob der Build erfolgreich war. Wenn du möchtest, dass dein Build fehlschlägt, weil die Tests veraltete Funktionalität von PHPUnit verwenden, konfiguriere failOnWarning="true" in phpunit.xml. Dies weist PHPUnit an, mit Shell-Exit-Code 1 zu beenden, wenn veraltete Assertions verwendet werden.

assertEquals()

Zweifellos ist assertEquals() die am häufigsten verwendete Assertion-Methode. Im Laufe der Jahre wurden ihrer API viele optionale Parameter hinzugefügt. Einige davon können nicht einmal zusammen verwendet werden, was eine ständige Quelle für Edge-Case-Bugs in der zugrunde liegenden Implementierung ist. Das Hauptproblem mit langen Listen optionaler Parameter ist, dass man die ersten vier angeben muss, wenn man den fünften verwenden möchte. Und ehrlich gesagt: Wer kann sich merken, wofür diese Parameter sind? Zumindest ich nicht.

Um Abhilfe zu schaffen, wurden in PHPUnit 7.5 spezialisierte Alternativen zu assertEquals() eingeführt. Deshalb sind in PHPUnit 8 die folgenden optionalen Parameter von assertEquals() veraltet:

  • $delta: verwende stattdessen assertEqualsWithDelta()
  • $canonicalize: verwende stattdessen assertEqualsCanonicalizing()
  • $ignoreCase: verwende stattdessen assertEqualsIgnoringCase()
  • $maxDepth hatte keine Wirkung mehr, da die Funktionalität, die er steuerte, bereits vor langer Zeit entfernt worden war

Diese Parameter werden in PHPUnit 9 entfernt.

Die Probleme mit assertEquals() und seinen optionalen Parametern ähneln der Situation, die wir in der Vergangenheit mit getMock() hatten. createMock(), createPartialMock() usw. wurden erstellt, um die verwirrende API von getMock() durch klare, explizite und separate Methoden zu ersetzen.

Der Vollständigkeit halber sei erwähnt, dass alles oben Gesagte auch für assertNotEquals() gilt, das das Gegenteil von assertEquals() ist.

assertContains()

Im Laufe der Jahre wurden auch der Methode assertContains() optionale Parameter hinzugefügt. Darüber hinaus wurde ihr Anwendungsbereich von der Arbeit mit Arrays und Objekten, die das Iterator-Interface implementieren, auf Strings erweitert. Dies führte immer wieder zu Problemen, da verschiedene, manchmal sogar widersprüchliche Anwendungsfälle in derselben Codeeinheit implementiert wurden.

Um diese Probleme zu beheben, wurden in PHPUnit 7.5 spezialisierte Alternativen eingeführt, die nur auf Strings arbeiten. In PHPUnit 8 sind die optionalen Parameter $checkForObjectIdentity, $checkForNonObjectIdentity und $ignoreCase nun veraltet. Darüber hinaus ist die Verwendung von assertContains() mit String-Haystacks ebenfalls veraltet. Tests, die assertContains() mit String-Haystacks verwenden, sollten so umstrukturiert werden, dass stattdessen assertStringContainsString() verwendet wird.

Diese Parameter sowie die Möglichkeit, assertContains() mit String-Haystacks zu verwenden, werden in PHPUnit 9 entfernt.

Beim Aufräumen von assertContains() habe ich einen Fehler gemacht, der es unmöglich machte, zu prüfen, ob ein Objekt in einem Iterable enthalten ist, während == statt === verwendet wird. Dieser Fehler wird durch die Einführung von assertContainsEquals() und assertNotContainsEquals() in PHPUnit 8.0.2 korrigiert. Danke an Rathes Sachchithananthan, dass er mich darauf aufmerksam gemacht hat.

All das gilt auch für assertNotContains(), das das Gegenteil von assertContains() ist.

assertInternalType()

Die Methode assertInternalType() kann verwendet werden, um zu prüfen, ob eine Variable einen Wert eines bestimmten nicht-benutzerdefinierten Typs enthält. Hier ist ein Beispiel, das zeigt, wie geprüft wird, ob eine Variable einen Wert vom Typ Array enthält:

$this->assertInternalType('array', $variable);

Ein wesentlicher Aspekt der Absicht dieser Assertion ist in einem Parameter versteckt: 'array'. Dies ist problematisch, da eine IDE beim Wort array keine Autovervollständigung anbieten kann. Darüber hinaus ist kein wirklicher Schutz vor einem Tippfehler in 'array' möglich, da es sich schließlich nur um einen String handelt.

Wie du vielleicht vermutet hast, wurden in PHPUnit 7.5 zur Behebung dieses Problems spezialisierte Alternativen zu assertInternalType() eingeführt. Diese neuen Methoden verbergen keine wichtigen Informationen in einem Parameter, sondern machen in ihrem Namen explizit, worum es geht:

$this->assertIsArray($variable);

Eine explizitere API bedeutet, dass es mehr Methoden gibt, aber jede Methode ist einfacher, da sie nur einen bestimmten Anwendungsfall implementieren muss. Dies führt zu Code, der leichter zu lesen und zu verstehen ist. Er ist auch einfacher zu schreiben, da die IDE jetzt die gesamte Absicht und nicht nur einen Teil davon autovervollständigen kann.

Alles oben Gesagte gilt auch für assertNotInternalType(), das das Gegenteil von assertInternalType() ist.

assertArraySubset()

Die Methode assertArraySubset() war eine ständige Quelle von Verwirrung und Frustration, wie man hier, hier, hier, hier, hier, hier oder hier sehen kann.

Der Grund für diese Situation ist, dass ich den ursprünglichen Pull-Request nach nur einer flüchtigen Überprüfung zusammengeführt habe. Ich hatte keinen Anwendungsfall dafür, aber die Implementierung sah zumindest auf den ersten Blick so aus, als würde sie dem Anwendungsfall der Person entsprechen, die diese neue Funktionalität vorgeschlagen hatte.

Im Laufe der Jahre führten Änderungen, die ich als Bugfixes für Edge Cases wahrnahm, zu Verhalten, das verwirrend war, weil wieder widersprüchliche Anwendungsfälle behandelt wurden. Die Unterstützung für einen dieser neuen Anwendungsfälle jetzt zu entfernen, würde einen Bruch der Rückwärtskompatibilität darstellen.

Während der Arbeit an PHPUnit 8 kam ich zu dem Schluss, dass assertArraySubset() nicht repariert werden kann, zumindest nicht ohne die Rückwärtskompatibilität zu brechen. Deshalb habe ich beschlossen, assertArraySubset() jetzt als veraltet zu markieren und es im nächsten Jahr aus PHPUnit 9 zu entfernen. Dies führt zu einem Bruch der Rückwärtskompatibilität, der explizit und offensichtlich ist. Zu ändern, wie assertArraySubset() wieder einmal funktioniert, wäre problematischer gewesen.

Alle, die der Meinung sind, dass diese Funktionalität nützlich ist, sind herzlich eingeladen, den Code zu nehmen, ihn in ein separates Projekt einzubetten und als Erweiterung für PHPUnit zu paketieren.

Nicht-öffentliche Eigenschaften

Objekte, die problematische Abhängigkeiten haben und im Allgemeinen zu groß sind, sind in Legacy-Code häufig anzutreffen. Solche Objekte können tatsächlich indirektes Testen durch die Inspektion von nicht-öffentlichem Zustand erfordern, auch wenn dies nie als empfohlene Best Practice galt. PHPUnit bietet einige Assertions, die auf nicht-öffentliche Eigenschaften arbeiten, wie assertAttributeEquals(). Diese Assertions wurden ursprünglich implementiert, um das Testen von Legacy-Code zu erleichtern.

Leider wurden diese Assertions oft missbraucht, um auch neuen Code zu testen. Der Fehler, den Entwicklerinnen und Entwickler dabei machen, ist, den Inhalt einer privaten oder geschützten Eigenschaft prüfen zu wollen. Anstatt diesen Weg zu gehen, sollten sie sich mehr auf das Verhalten von Objekten konzentrieren, anstatt ihren Zustand zu prüfen.

Es zeigt sich, dass das Bereitstellen von Funktionalität wie assertAttributeEquals() von Haus aus schlechte Test-Praktiken fördert. Deshalb habe ich beschlossen, alle Assertions sowie Hilfsmethoden, die auf nicht-öffentlichen Eigenschaften arbeiten, in PHPUnit 8 als veraltet zu markieren. In PHPUnit 9 werden sie entfernt.

Denke daran: Methoden, die nicht öffentlich sind, dürfen nicht direkt durch Aufruf über die Reflection API getestet werden. Der private Zustand eines Objekts darf auch nicht in einem Test geprüft werden. Dadurch entsteht eine enge Kopplung zwischen deinem Testcode und dem Produktionscode, den du testest. Du testest die Implementierung, nicht die API und das Verhalten. Sobald sich die Implementierung ändert, bricht der Test, weil er auf private Implementierungsdetails gesetzt hat.

Exceptions erwarten

Fast auf den Tag genau vor drei Jahren habe ich über Best Practices für das Erwarten von Exceptions mit PHPUnit geschrieben. Damals habe ich vorgeschlagen, $this->expectException(MyException::class); innerhalb einer Testmethode zu verwenden, anstatt die Annotation @expectedException MyException auf der Testmethode zu nutzen. Die Begründung für diesen Vorschlag wird im referenzierten Artikel ausführlich erklärt. Hier ist die Zusammenfassung:

Einerseits unterstützt PHP keine nativen Annotations. Stattdessen werden Annotations innerhalb von Code-Kommentaren ausgedrückt. In einem Code-Kommentar kannst du keinen unqualifizierten Klassennamen verwenden, also beispielsweise Example statt vendor\project\Example, den du im Code verwenden könntest, wenn diese Klasse im aktuellen Namespace ist oder importiert wurde.

Andererseits betrachtet PHPUnit den Test als erfolgreich, wenn die @expectedException-Annotation verwendet wird und die angegebene Exception zu einem beliebigen Zeitpunkt während der Ausführung der Testmethode ausgelöst wird. Das ist fast nie das, was du willst.

Ich denke heute, dass es mehr schadet als nützt, mehrere Wege zum Erwarten von Exceptions in PHPUnit zu unterstützen. Die Verwendung von @expectedException und verwandten Annotations ist in PHPUnit 8 veraltet, und die Unterstützung für diese Annotations wird in PHPUnit 9 entfernt.

Upgrade auf PHPUnit 8

PHPUnit folgt semantischer Versionierung. Kurz gesagt bedeutet das, dass du dich auf die folgenden drei einfachen Regeln zur Interpretation von PHPUnits Versionsnummern verlassen kannst:

  • Die Major Version wird erhöht, wenn es inkompatible Änderungen gibt, zum Beispiel wenn sich die öffentliche API ändert
  • Die Minor Version wird erhöht, wenn neue Funktionalität auf rückwärtskompatible Weise hinzugefügt wird
  • Die Patch Version wird erhöht, wenn Fehler auf rückwärtskompatible Weise behoben werden

Da wir gerade beim Thema semantische Versionierung sind, interessiert dich vielleicht ein Artikel, den ich darüber geschrieben habe, wie man PHPUnit korrekt als Abhängigkeit angibt, wenn du Composer zur Verwaltung deiner Projektabhängigkeiten verwendest.

Ein Tool wie PHPUnit darf nicht blindlings von einer Hauptversion zur anderen aktualisiert werden. Schließlich würdest du auch nicht auf eine neue PHP-Hauptversion upgraden – ohne dich über die Änderungen an der Sprache zu informieren – und erwarten, dass dein Code ohne Anpassungen weiterhin funktioniert. Oder doch? Dasselbe gilt für das Framework, auf dem deine Anwendung aufgebaut ist, und die Bibliotheken, die du verwendest.

Wenn ich Klagen höre, dass Tests nicht mehr funktionieren oder Änderungen erfordern, nur einen Werktag nach der Veröffentlichung einer Hauptversion wie PHPUnit 8, kann ich nur annehmen, dass Entwicklerinnen und Entwickler das Upgrade völlig ungeplant durchgeführt haben.

Du solltest nicht auf eine neue PHPUnit-Hauptversion upgraden, ohne dich über die eingeführten Breaking Changes zu informieren. Du solltest auch nicht damit beginnen, dein Projekt auf eine neue Version deines Test-Frameworks zu migrieren, wenn gerade einige deiner Tests fehlschlagen.

Führe deine Tests zunächst nur lokal und nicht in Continuous Integration mit der neuen PHPUnit-Version aus. Notiere Warnungen, besonders in Bezug auf veraltete Funktionalität, und Fehler. Lies dann die relevanten Änderungen in den Release Notes und lerne, wie du deinen Code an die neue Version anpassen musst. Nimm die erforderlichen Änderungen beispielhaft für einen oder zwei Tests jeder Fehlerkategorie vor. Du solltest dann in der Lage sein abzuschätzen, wie viel Zeit erforderlich ist, um alle notwendigen Änderungen vorzunehmen.

Viele dieser Änderungen können automatisiert werden. PHP-CS-Fixer ist das bevorzugte Tool, um schnell und zuverlässig Änderungen an einer gesamten Codebasis vorzunehmen. PHP-CS-Fixer kann beispielsweise automatisch eine void-Rückgabetypdeklaration zu allen Funktionen und Methoden einer Codebasis hinzufügen, die keine return-Anweisung in ihrem Körper haben.

php-cs-fixer wird schließlich in der Lage sein, Code wie assertInternalType('array', $variable) automatisch zu assertIsArray($variable) zu migrieren. Die Arbeit zur Unterstützung von Entwicklerinnen und Entwickler, die ihre Tests von PHPUnit 7 auf PHPUnit 8 migrieren, wird hier, hier und hier verfolgt.

Rector ist ein weiteres Tool, das einen Blick wert ist. Es unterstützt viele „Instant Upgrade"-Refactorings, wie das Ändern von assertEquals()-Aufrufen, die optionale Methodenparameter verwenden, oder das Umschreiben von assertInternalType()-Aufrufen zu den jeweiligen Alternativen.

Fazit

Die Frustration der Entwicklerinnen und Entwickler, die ihrer Abneigung gegen PHPUnit 8 Ausdruck verliehen haben, liegt nicht in den technischen Änderungen dieser neuen Version begründet, sondern im Fehlen eines angemessenen Prozesses für das Upgraden der Abhängigkeiten in einem Projekt.

Die Tatsache, dass Entwicklerinnen und Entwickler eine void-Rückgabetypdeklaration zu Template-Methoden wie setUp() in ihren Testfallklassen hinzufügen müssen, wurde vor einem Jahr beim Release von PHPUnit 7 angekündigt. Die notwendigen Änderungen hätten zu jedem Zeitpunkt zwischen der Veröffentlichung von PHPUnit 7 und der Veröffentlichung von PHPUnit 8 vorgenommen werden können. Das hätte kein Problem verursacht und nicht viel Zeit gekostet. Erst recht nicht, wenn ein Tool wie PHP-CS-Fixer verwendet worden wäre, um die void-Rückgabetypdeklarationen automatisch hinzuzufügen.

Die Release-Ankündigung für PHPUnit 8 enthält ähnliche Informationen für PHPUnit 9, das im Februar 2020 fällig ist. Manchmal habe ich jedoch den Eindruck, dass niemand solche Ankündigungen liest.

Die Wartung eines Softwareprojekts darf nicht nur reaktiv sein. Sie muss auch proaktiv sein in dem Sinne, dass Entwicklerinnen und Entwickler sich über Änderungen im Klaren sein müssen, die sie in den nächsten zwölf Monaten vornehmen müssen, um unangenehme Überraschungen zu vermeiden, wenn eine neue Hauptversion der Programmiersprache oder eines Frameworks, einer Bibliothek oder eines Tools veröffentlicht wird, das sie verwenden.

Das bedeutet zumindest, die Release-Ankündigungen neuer Hauptversionen sorgfältig zu lesen und die für einen bevorstehenden Sprint erforderlichen Änderungen einzuplanen. Idealerweise verfolgen Entwicklerinnen und Entwickler die Entwicklung wichtiger Drittanbieter-Softwarepakete, von denen ihr Projekt abhängt. Im Fall von PHPUnit würde das bedeuten, den Milestone für die nächste Hauptversion ab und zu zu besuchen. Dies gibt ihnen nicht nur die Möglichkeit, frühzeitig von bevorstehenden Änderungen zu erfahren, sondern auch Feedback an das Upstream-Projekt zu diesen Änderungen zu geben.