Snapshot-Testing für PHP-gerenderte Views
Vor kurzem habe ich den Artikel What Is Snapshot Testing, and Is It Viable in PHP? von Christopher Pitt gelesen. Von Snapshot Testing hatte ich bis dahin noch nie gehört. Unter anderem fand ich interessant, wie er das Rendering von Templates testet.
It’s clear what the template should produce, given repeatable initial information. We could even mock the initial data, and assert the output.
Er nimmt einen kleinen HTML-Schnipsel, in den einige PHP-Variablen eingesetzt werden, befüllt diese einmal mit definierten Werten, und lässt einen Snapshot davon erstellen. Beim nächsten Testlauf lässt sich der Output mit diesem Snapshot vergleichen. Haben sich der HTML-Code oder die Werte aus welchen Gründen auch immer geändert, schlägt der Test fehl.
Mit spatie/phpunit-snapshot-assertions gibt es eine Bibliothek, die Snapshot-Testing mit PHPUnit bzw. Codeception möglich macht. Da ich gerade einige Codeception-Tests für ein älteres Zend Framework 1-Projekt geschrieben habe, das ich teilweise refactoren wollte, kam mir die Lib gerade recht.
Im Projekt gab es einige Templates/View-Skripte, die sehr unübersichtlich waren, so dass ich bestimmt Elemente in Subtemplates, so genannte Partials, auslagern wollte. Beispielsweise gab es eine DateTimePicker-Komponente, die optional mit einem Datum vorbelegt werden konnte. Den HTML-Code extrahierte ich in ein eigenes Template und schrieb (ungefähr) folgenden Test dafür:
<?php
use Codeception\Test\Unit;
use Spatie\Snapshots\MatchesSnapshots;
/**
* Class DateTimePickerTest
*/
class DateTimePickerTest extends Unit
{
use MatchesSnapshots;
/**
* @var Zend_View
*/
protected $view;
/**
* Setup
*/
public function _before()
{
$this->view = new Zend_View();
$this->view->setScriptPath(ROOT . '/application/modules/acme/views/scripts/');
}
/**
* @dataProvider dateTimeProvider
*
* @param DateTime $dateTime
*/
public function testSnapshot(\DateTime $dateTime)
{
$this->view->date = $dateTime;
$result = $this->view->render('partials/datetimepicker.phtml');
$this->assertMatchesSnapshot($result);
}
/**
* @return array
*/
public function dateTimeProvider()
{
return [
[new \DateTime()],
[\DateTime::createFromFormat('Ymd', '20170514')],
];
}
/**
* Overwrite trait method to force custom snapshot directory
* @return string
*/
public function getSnapshotDirectory()
{
$unitDir = ROOT . 'tests/unit/';
$dir = dirname((new ReflectionClass($this))->getFileName());
$subDir = str_replace($unitDir, '', $dir);
$snapshotDir = ROOT . 'tests/_snapshots/' . $subDir;
if (!is_dir($snapshotDir)) {
mkdir($snapshotDir, 0775, true);
}
return $snapshotDir;
}
}
Der Test wird mit den Daten aus den Data Provider zwei Mal durchlaufen, also werden auch zwei Snapshots erstellt - einmal mit dem aktuellen Datum, einmal mit dem 14. Mai 2017. Wird jetzt beispielsweise dem Input-Element im Partial eine weiteres class
-Attribut hinzugefügt, schlägt der Test fehl.
Der Snapshot-Test nimmt mir in diesem Fall einiges an Schreibarbeit ab, denn ohne ihn müsste ich entweder ich den gerenderten String irgendwie parsen oder mit einer im Code festgeschriebenen Vorlage vergleichen, oder ich müsste einen komplett neuen Akzeptanztest schreiben (na gut, so groß wäre der Aufwand hier nun auch wieder nicht).
Andererseits ist zu bedenken, dass in einer Entwicklungsphase, in der sich die Views noch häufig ändern, der Snapshot-Test schon nerven kann. Außerdem sollten die Snapshots mit ins VCS wandern und muss nach jeder Anpassung ebenfalls committet werden.
Snapshot-Tests funktionieren aber nicht nur für View-Output, sondern auch für anderen Code, wie Pitt beschreibt. Im Endeffekt muss man sich aber genau überlegen, ob sie im eigenen Projekt sinnvoll sind. Pitt bezeichnet sie als brittle (zerbrechlich, spröde), und das trifft es ganz gut. Aber er hat auch recht, wenn er schreibt:
It’s OK that these tests are brittle because they’re easy to regenerate, and still useful in many ways.
Was haltet ihr von Snapshot-Tests? Setzt ihr sie schon ein oder denkt darüber nach, sie einzusetzen?
Kommentare
Ansicht der Kommentare: Linear | Verschachtelt