Repository für meinen Workshop beim WordCamp Catania 2019
Optional, aber möglicherweise müssen Sie Docker installieren:
sudo apt-get install docker-ce docker-ce-cli containerd.io
Ich gehe davon aus, dass Sie Composer installiert haben. Lassen Sie uns zuerst PHPUnit installieren:
composer require --dev phpunit/phpunit ^8.3
Bitte prüfen Sie auch die Voraussetzungen!
PHPUnit 8.3 erfordert mindestens PHP 7.2! Übrigens – der Sicherheitssupport für PHP 7.1 endet am 1. Dezember 2019.
Hinweis : Sie haben Composer nicht installiert? Probieren Sie es aus!
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
Es gibt mindestens zwei gültige Frameworks, die nützlich sind, wenn Sie WordPress-Erweiterungen testen möchten:
Versuchen wir es mit Brain Monkey :
composer require --dev brain/monkey:2.*`
Dadurch werden automatisch auch Mockery und Patchwork installiert. Führen Sie einfach composer install
aus und schon kann es losgehen.
Erstellen Sie ein Verzeichnis, das einer kleinen Testklasse namens WcctaTest.php ein Zuhause bietet:
mkdir -p tests/wccta
Exzellent! Erstellen wir nun eine Konfigurationsdatei phpunit.xml im Stammverzeichnis.
Sie können Ihre Tests auch mit den Konfigurationsparametern über die Befehlszeile ausführen. Siehe den nächsten Teil (Hinweis: „Skripte“)!
Großartig! Fügen Sie der Datei „composer.json“ einige Abschnitte hinzu:
composer test
eingeben Erstellen wir ein Verzeichnis, das unserem Quellcode ein Zuhause bietet. Hier platzieren Sie eine erste Klasse, die Sie bald testen werden.
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
Wir wollen einige Methoden der Klasse Plugin
testen. Stellen Sie sich eine Methode namens is_loaded
vor, die bei Erfolg true
zurückgibt. Wenn Sie bereit sind, führen Sie Folgendes aus:
composer test
Hinweis : Ihr System oder Ihre PHP-Version ist nicht auf dem neuesten Stand? Sie könnten diesen Schritt einfach überspringen, aber probieren wir etwas (nicht so) Neues aus!
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
Sie können sich wahrscheinlich vorstellen, dass einige Plugins viele Klassen haben und Sie leicht vergessen können, alle Funktionen zu testen, die getestet werden müssen.
Reden wir also über die Abdeckung !
Fügen Sie einfach einen benutzerdefinierten Befehl zum Abschnitt „scripts“ in Ihrer Composer.json hinzu:
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
und einen Filter für Ihre phpunit.xml :
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
Führen Sie jetzt einfach composer coverage
aus! Dadurch wird ein Verzeichnis ./reports/php/coverage
zusammen mit einigen HTML-Dateien erstellt. Nun ja, nicht auf allen Computern. Einige erhalten weiterhin Fehlermeldungen wie:
Error: No code coverage driver is available
Lassen Sie uns das in unserem Docker-Image beheben. Ich habe eine Docker-Datei vorbereitet, damit Sie Folgendes einfach ausführen können:
docker build -t coverage .
Und nachdem der Build-Prozess abgeschlossen ist:
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
Jetzt kennen Sie Kung Fu! Bitte öffnen Sie die Datei ./reports/php/coverage/index.html in Ihrem Browser!
Lassen Sie uns unsere Plugin
-Klasse mit dem Plugin verbinden. Bevor wir wirklich mit dem Testen beginnen, zeige ich Ihnen nur, wie Sie Teile Ihres Codes als nicht testbar deklarieren.
@codeCoverageIgnore
Dies ist eine der wichtigen verfügbaren Anmerkungen. Wir werden später darauf zurückkommen, aber zuerst:
Führen Sie die Unittests erneut mit dem Coverage-Report durch!
Vielleicht ist Ihnen die Spalte CRAP
im Berichterstattungsbericht aufgefallen. CRAP ist ein Akronym für Change Risk Anti-Patterns . Es gibt an, wie riskant eine Codeänderung in einer Klasse oder Methode sein kann. Mit weniger komplexem Code und vollständiger Abdeckung durch Tests können Sie das Risiko (und damit den Index) senken.
Fangen wir an, etwas zu testen. Aber was? Es sind noch keine weiteren Funktionen geschrieben, die getestet werden müssen.
Hier kommt TDD (Test Driven Development) ins Spiel.
Auch wenn Sie sich gegen die Anwendung dieser Technik entscheiden, sollten Sie zumindest wissen, wovon wir sprechen.
Erstellen wir zunächst einen Test CarTest
, der testen soll, ob die Methode get_price
den String '€ 14.500'
zurückgibt. Erstellen Sie dann ein Car
und schreiben Sie die Methode get_price
, die den Test erfüllt . Beginnen Sie nicht mit der Implementierung.
An dieser Stelle möchte ich auch das in TDD weithin akzeptierte Testmuster AAA (Arrange Act Assert) vorstellen. Es beschreibt, wie ein Test organisiert wird, und ist GWT (Given When Then) von BDD (Behavior-driven Development) sehr ähnlich.
Sie können Ihre Klassen testen, ob sie unter bestimmten Bedingungen eine Ausnahme auslösen. Lassen Sie uns nun die get_price
-Methode implementieren.
Erstellen Sie einfach eine Registry
Klasse, die einen gemischten Wert als benanntes Element in einem internen Array festlegt. Verwenden Sie dazu eine Methode set()
oder die magische Methode __set()
. Nehmen Sie zunächst an, dass wir ein JSON-Objekt an unsere Car
-Klasse übergeben können. Dies wird unserer Klasse etwas mehr Wert verleihen.
Eine andere Methode get
oder __get()
soll prüfen, ob ein Element mit einem gegebenen Wert existiert und es bei Erfolg zurückgeben. Wenn kein solches Element vorhanden ist, lösen Sie eine OutOfBoundsException
aus. Schreiben Sie nun einen Konstruktor, der die JSON-Eingabe verarbeitet und das Objekt in einer Member-Var data
speichert. Die get_price
-Methode sollte den Preis aus der data
übernehmen und sich um die formatierte Ausgabe kümmern.
Schauen Sie sich Verzweigungsschritt 10 an, wenn Sie Schwierigkeiten haben, den Code zu schreiben! Der variable price
sollte eine ganze Zahl sein. Dies ist im Moment wahrscheinlich kein Problem, da Sie die PHP-Funktion number_format()
verwenden können, um die korrekte Ausgabe zu erstellen. Bei einer WordPress -Installation wird jedoch davon ausgegangen, dass das Gebietsschema beispielsweise auf it_IT
(Italienisch) eingestellt ist.
Der richtige Weg, Zahlen in WordPress zu formatieren, ist die Verwendung der Funktion number_format_i18n()
.
Also lasst uns das ändern und sehen, was passiert:
Error: Call to undefined function wcctanumber_format_i18n()
Wir werden das gleich beheben, aber bereiten wir es zunächst ein wenig vor. Brain Monkey verwendet setUp()
und tearDown()
von PHPUnit . Sie können diese Methoden überschreiben. Erstellen wir einen benutzerdefinierten TestCase
– nennen wir ihn WcctaCase
– den wir erweitern können, da wir dies wahrscheinlich in jeder Testklasse tun werden.
Fügen wir nun den Namespace für Tests in den Abschnitt autoload-dev ein:
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
Zum Schluss ändern wir das übergeordnete Element unserer Testklassen.
class CarTest extends WcctaTestCase { // ... }
Wir sind bereit, unsere erste WordPress -Funktion mit auszuprobieren
Functionsexpect( $name_of_function )->andReturn( $value );
Einen Test für nur eine Erwartung zu schreiben, scheint zu aufwändig zu sein. Was ist, wenn Sie verschiedene Werte testen möchten?
Datenanbieter zur Rettung. Über Anmerkungen habe ich bereits in Schritt 5 gesprochen. Diese ist auch sehr nützlich:
@dataprovider method_that_returns_data
Schauen Sie sich mein Beispiel an. getData
gibt ein Array von Arrays zurück. Jedes dieser Arrays enthält 3 Werte. Unsere test_getPrice
-Methode kann also nicht nur den Datenprovider mit der Annotation akzeptieren, sondern auch die Eingabevariablen als Parameter definieren.
Sie können Ihre Klassen testen, ob sie unter bestimmten Bedingungen eine Ausnahme auslösen.
Erstellen Sie einfach eine Registry
Klasse, die einen gemischten Wert als benanntes Element in einem internen Array festlegt. Verwenden Sie dazu eine Methode set()
oder die magische Methode __set()
.
Eine andere Methode get
oder __get()
soll prüfen, ob ein Element mit einem bestimmten Schlüssel existiert und es bei Erfolg zurückgeben. Wenn kein solches Element vorhanden ist, lösen Sie eine OutOfBoundsException
aus.
Schauen Sie sich Verzweigungsschritt 10 an, wenn Sie Schwierigkeiten haben, den Code zu schreiben!
Die letzten Schritte führten uns zu den Fabriken . Was ist eine Fabrik? Manchmal erstellen Sie Funktionen oder Methoden, die den komplexen Prozess zum Erstellen eines bestimmten Objekts einfach verbergen. Und manchmal muss man sich entscheiden, welche Art von Objekt man erstellen möchte.
In WordPress-Plugins füge ich am liebsten Hooks in Factorys zu Objekten hinzu. Es gibt Plugins, die Hooks in Klassenkonstruktoren hinzufügen. Das ist keine gute Sache (vor allem, wenn man immer noch den klassischen Weg testet – das Erstellen einer kompletten Umgebung mit laufendem WordPress).
Erstellen wir eine Klasse Factory
mit einer statischen Funktion namens create
. Diese Methode sollte ein Car
Objekt zurückgeben. Aber lasst uns den Konstruktor von Car
so umgestalten, dass er bereits ein Objekt und keinen JSON-String erwartet. Wir werden dies stattdessen in der create-Methode der Factory
-Klasse tun.
Testen Sie Ihr Plugin jetzt mit composer test
und Sie werden einige Fehler sehen:
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
Wir sollten auch unsere Tests korrigieren...
Exzellent! Lassen Sie uns einen Test für unsere Fabrik erstellen. Wir lassen die Methode vorerst ohne Inhalt. Führen Sie die Tests erneut durch!
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
Die Tests sind erfolgreich, aber Sie erhalten die Meldung, dass es sich um einen riskanten Test handelte. Übrigens: Benennen Sie die Funktion test_create
create
einfach und verwenden Sie die Annotation @test
. Ich glaube, dass die Verwendung dieser Anmerkung von Ihrem persönlichen Geschmack abhängt!
Wir werden nun etwas tiefer darauf eingehen.
Erstellen Sie eine Schnittstelle FooterInterface
, die eine öffentliche info
definiert, die keinen Rückgabewert erwartet. Wenn Sie die Schnittstelle in Car
implementieren, könnte info
beispielsweise eine lustige Nachricht ausgeben.
Definieren Sie den Rückgabetyp FooterInterface
für die create
-Methode von Factory
und fügen Sie die info
-Methode von Car
zur WordPress-Aktion wp_footer
hinzu.
Lassen Sie uns dies nun im FactoryTest
testen. Es gibt mindestens zwei Möglichkeiten, dies richtig zu testen. Verwenden Sie has_action oder ActionsexpectAdded()
. Ein Test für Filter wäre ähnlich und ist auf der verlinkten Seite gut beschrieben.
Überprüfen Sie, ob composer test
immer noch alle Tests besteht.
Wie ist die Berichterstattung derzeit? Führen Sie composer coverage
aus und überprüfen Sie die generierte Ausgabe.
Die info
-Methode unserer Car
-Klasse wird von keinem Test abgedeckt. Aber können wir die Ausgabe einer Methode testen?
Es stellt sich heraus, dass es mit ExpectOutputString ganz einfach ist.
Feiern wir, was wir gelernt haben!
Erstellen Sie eine Klasse Locale
mit einer öffentlichen Methode get
die get_locale()
zurückgibt. Schließen Sie die Methode von der Berichterstattung aus!
Erstellen Sie nun einen Konstruktor in unserer Plugin
-Klasse, der eine Locale
Instanz akzeptiert, und speichern Sie sie in einer Member-Variable $this->locale
. Erstellen Sie dann eine Methode get_region_code
, die den Wert von $this->locale->get()
zurückgibt. Ah, und entfernen Sie die is_loaded
-Methode. ;)
In unserem Test konnten wir ein Objekt vom Typ Locale
erstellen, die WordPress-Funktion get_locale
verspotten und an den Plugin
-Konstruktor übergeben! Aber ich möchte Mocker hier verwenden:
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
Jetzt können Sie Ihre WordPress-Plugins kugelsicher machen!
Viel Spaß!