Statische Tests in Magento 2: Magento Coding Standard, Dependency- und Legacy-Checks
AI generated
M2
di.xml
Magento 2 · Statische Tests · Coding Standard · QA
Statische Tests in Magento 2:
Coding Standard, Dependency und Legacy im Griff

Statische Tests decken in Magento 2 genau die Fehlerklasse auf, die weder PHPStan noch PHPUnit sehen: Verstöße gegen den Magento Coding Standard, undeklarierte Abhängigkeiten zwischen Modulen und schleichend wieder eingeschleppte Legacy-Patterns. Wer die eigene Suite unter dev/tests/static ernst nimmt, verhindert genau die Art von strukturellen Fehlern, die erst beim nächsten Major-Update oder bei einer geänderten Modul-Ladereihenfolge sichtbar werden.

15 Min. Lesezeit phpcs · Dependency Test · Legacy Test · CI Magento 2.4.8 · PHP 8.4

1. Statische Tests in Magento 2: Einordnung und Abgrenzung

Statische Tests sind in Magento 2 keine einzelne Datei und kein einzelnes Werkzeug, sondern eine eigene, von Adobe direkt im Core mitgelieferte Testkategorie unter dev/tests/static. Anders als PHPUnit-Tests, die Verhalten zur Laufzeit prüfen, laufen statische Tests ohne eine einzige Zeile Code auszuführen: sie parsen Quelldateien, XML-Konfigurationen und Layout-Dateien und werten sie strukturell aus. Diese statischen Tests sind fest in drei Kernbereiche gegliedert, die in diesem Artikel im Zentrum stehen: der Magento Coding Standard über phpcs, der Dependency-Test gegen undeklarierte Modulabhängigkeiten und der Legacy-Test gegen veraltete Code-Patterns.

Wichtig ist die Abgrenzung zu zwei benachbarten, aber klar getrennten Werkzeugen. PHPStan ist ein externes Analyse-Tool, das Typinformationen und Datenflüsse prüft und über eigene Level und Baselines konfiguriert wird, das ist bewusst nicht Thema dieses Artikels. PHPUnit wiederum prüft mit Unit- und Integrationstests tatsächliches Laufzeitverhalten von Klassen und Modulen. Statische Tests dagegen prüfen weder Typen noch Verhalten, sondern Konventionen, Strukturintegrität und die Einhaltung des Magento Coding Standard, unabhängig davon, ob der Code beim Ausführen überhaupt funktioniert.

Der praktische Wert dieser statischen Tests liegt genau in dieser Nische: Der Dependency-Test findet zum Beispiel ein Modul, das sich beim lokalen Testen völlig normal verhält, aber bei einer anderen Modul-Ladereihenfolge in der Produktion bricht, weil eine Abhängigkeit nie deklariert wurde. Der Magento Coding Standard wiederum erzwingt Konventionen, die für die Wartbarkeit über Jahre entscheidend sind, etwa den Verzicht auf direkte ObjectManager-Aufrufe. Kein anderes Werkzeug in der Magento-Toolchain deckt genau diese Kombination aus Konventions- und Strukturprüfung ab.

2. Magento Coding Standard: phpcs-Ruleset und Sniff-Kategorien

Der Magento Coding Standard wird technisch über das Composer-Paket magento/magento-coding-standard ausgeliefert, ein Regelwerk für PHP_CodeSniffer, das Adobe selbst für den Core und für zertifizierte Extensions verwendet. Wer phpcs --standard=Magento2 gegen ein eigenes Modul laufen lässt, prüft damit exakt die Konventionen, die auch bei der Marketplace-Technical-Review angewendet werden. Der Magento Coding Standard ist dabei deutlich strenger als generische PHP-Standards wie PSR-12, weil er zusätzlich Magento-spezifisches Wissen einbringt.

Die Sniffs im Magento Coding Standard sind in fachliche Kategorien gruppiert: Security-Sniffs erkennen unsichere Konstrukte wie direkte SQL-String-Konkatenation, Performance-Sniffs flaggen ineffiziente Muster wie Datenbankzugriffe in Schleifen, und PHP-Sniffs erzwingen etwa den Verzicht auf @-Error-Silencing oder var_dump in produktivem Code. Eine eigene Kategorie widmet sich reinen Magento-Konventionen, etwa dass Konstruktoren keine direkte ObjectManager::getInstance()-Instanziierung enthalten dürfen, sondern konsequent Dependency Injection nutzen müssen.

Für eigene Module lohnt sich zusätzlich die Kombination mit dem Magento2-Doc-Comments-Sniff für vollständige PHPDoc-Blöcke, weil viele Teams intern strengere Dokumentationsanforderungen haben als der Standard-Ruleset vorschreibt. Die Installation läuft über eine reguläre Composer-Dev-Abhängigkeit, wodurch der Magento Coding Standard versioniert mit dem Projekt mitwächst.


{
  "require-dev": {
    "magento/magento-coding-standard": "^32.0",
    "squizlabs/php_codesniffer": "^3.9"
  },
  "extra": {
    "magento-force": "override"
  }
}

3. phpcs.xml einrichten und Magento Coding Standard ausführen

Damit der Magento Coding Standard nicht bei jedem Aufruf erneut per Kommandozeilen-Flag angegeben werden muss, legt man im Modul- oder Projektwurzelverzeichnis eine phpcs.xml an. Diese Datei referenziert den Ruleset-Namen Magento2, definiert die zu prüfenden Dateiendungen wie php und phtml und schließt Ordner wie vendor oder generierte Verzeichnisse explizit aus. Ohne eine solche Konfiguration prüft phpcs entweder zu viel, etwa Drittanbieter-Code, oder zu wenig, weil Template-Dateien mit der Endung .phtml vergessen werden.

Ein zweiter wichtiger Baustein ist der installed_paths-Eintrag, der PHP_CodeSniffer mitteilt, wo das Ruleset des Magento Coding Standard physisch liegt. Bei einer Composer-Installation übernimmt das Paket dies in der Regel automatisch über die eigene Composer\Installer-Integration, bei manuellen Setups muss der Pfad explizit über phpcs --config-set installed_paths gesetzt werden. Fehlt dieser Schritt, bricht phpcs mit einer Fehlermeldung ab, dass der Ruleset-Name Magento2 unbekannt sei.


<!-- phpcs.xml im Projekt- oder Modulwurzelverzeichnis -->
<?xml version="1.0"?>
<ruleset name="Mironsoft Magento Coding Standard">
    <description>Runs the Magento Coding Standard against custom modules</description>

    <!-- Reference the official Magento2 ruleset -->
    <rule ref="Magento2"/>

    <file>app/code/Mironsoft</file>

    <arg name="extensions" value="php,phtml"/>
    <arg name="colors"/>
    <arg value="p"/>

    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/var/*</exclude-pattern>
    <exclude-pattern>*/generated/*</exclude-pattern>
</ruleset>

Im laufenden Betrieb reicht danach ein einziger Aufruf über den Docker-Wrapper aus, um den kompletten Magento Coding Standard gegen das eigene Modul laufen zu lassen. Für Mark-Shust-Setups bietet sich ein eigener Wrapper wie bin/phpcs an, der intern gegen vendor/bin/phpcs im Container delegiert.


#!/usr/bin/env bash
# Run the Magento Coding Standard against a specific module
bin/phpcs app/code/Mironsoft/SeoSuite --standard=Magento2

# Auto-fix violations that phpcbf can safely resolve
bin/phpcbf app/code/Mironsoft/SeoSuite --standard=Magento2

# Run Magento's own static test suite (Dependency, Legacy, coding style)
bin/cli vendor/bin/phpunit -c dev/tests/static/phpunit.xml.dist \
  --filter "Magento\\Test\\Integrity\\Dependency"

bin/cli vendor/bin/phpunit -c dev/tests/static/phpunit.xml.dist \
  --filter "Magento\\Test\\Legacy"

4. Der Dependency-Test: undeklarierte Modulabhängigkeiten aufdecken

Der Dependency-Test ist einer der drei Kernbausteine der statischen Tests und prüft eine Frage, die weder der Magento Coding Standard noch PHPStan beantworten kann: Nutzt ein Modul Klassen, Blöcke, Layout-Handles oder Events eines anderen Moduls, ohne diese Abhängigkeit formal in module.xml zu deklarieren? Dazu durchsucht der Test statisch den kompletten PHP-, XML- und Template-Code eines Moduls, extrahiert jede Referenz auf fremde Klassen und Namespaces und gleicht sie mit den deklarierten Sequenzen ab.

Ein typischer Fall: Ein eigenes Modul injiziert in einem Konstruktor eine Klasse aus Magento\ConfigurableProduct, ohne dass Magento_ConfigurableProduct in der eigenen module.xml als Sequenz-Abhängigkeit gelistet ist. Lokal funktioniert das problemlos, weil beide Module ohnehin installiert sind und die Ladereihenfolge zufällig passt. Sobald sich aber die Modul-Reihenfolge ändert, etwa durch ein Update oder eine andere Modulkombination, kann die referenzierte Klasse zum Zeitpunkt des Ladens noch nicht verfügbar sein. Der Dependency-Test schlägt genau hier präventiv Alarm, lange bevor ein solcher Fehler live auftritt.

Praktisch meldet der Dependency-Test jeden Verstoß mit exaktem Dateipfad, Zeilennummer und dem Namen der undeklarierten Fremdklasse. Das macht die Fehlerbehebung eindeutig: entweder wird die fehlende Sequenz in module.xml ergänzt, oder die Abhängigkeit wird bewusst über eine Service-Contract-Schnittstelle statt einer konkreten Modellklasse aufgelöst, um die Kopplung von vornherein zu reduzieren.

5. Modul-Sequenzen in module.xml richtig deklarieren

Die module.xml jedes Magento-2-Moduls kennt neben dem reinen Namen auch einen sequence-Block, der festlegt, nach welchen anderen Modulen das eigene Modul geladen werden muss. Das ist keine Bestätigung einer Composer-Abhängigkeit, sondern eine eigenständige, für den Dependency-Test relevante Information: Composer regelt, welche Pakete überhaupt installiert werden, module.xml regelt, in welcher Reihenfolge sie innerhalb von Magento initialisiert werden. Beides muss synchron gehalten werden, ansonsten meldet der Dependency-Test weiterhin eine Lücke, selbst wenn Composer die richtige Version zieht.

Fehlt eine Sequenz, kann das zwei sichtbare Folgen haben: Die Dependency Injection Konfiguration in di.xml wird in einer Reihenfolge zusammengeführt, die nicht der fachlichen Erwartung entspricht, oder setup:upgrade-Skripte laufen vor den Skripten des Moduls, von dem sie eigentlich abhängen. Eine korrekt gepflegte Sequenz ist deshalb keine bürokratische Formalie, sondern die Grundlage dafür, dass Magentos Modulsystem deterministisch und reproduzierbar bleibt.


<!-- app/code/Mironsoft/SeoSuite/etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Mironsoft_SeoSuite">
        <sequence>
            <!-- Declares that Magento_Catalog must load before this module -->
            <module name="Magento_Catalog"/>
            <!-- Declares that Magento_ConfigurableProduct must load before this module -->
            <module name="Magento_ConfigurableProduct"/>
            <module name="Mironsoft_Core"/>
        </sequence>
    </module>
</config>

Eine bewährte Praxis ist es, die Sequenz nach jedem neuen Konstruktor-Import oder jeder neuen Layout-Referenz manuell zu überprüfen, statt sie nur einmal beim Modul-Anlegen zu pflegen. In der Praxis reicht dafür ein regelmäßiger Lauf des Dependency-Tests direkt während der Entwicklung, nicht erst kurz vor dem Release, weil sich Abhängigkeiten mit jeder neuen Funktion verschieben können.

6. Legacy-Test: veraltete Magento-Patterns automatisch erkennen

Der Legacy-Test ist der dritte Baustein der statischen Tests und prüft, ob ein Modul Klassen, Methoden oder Muster verwendet, die Adobe offiziell als veraltet oder als Altlast aus Magento 1 eingestuft hat. Anders als der Dependency-Test geht es hier nicht um fehlende Deklarationen, sondern um bewusst verbotene oder auslaufende Konstrukte, die in einer zukünftigen Major-Version ohne Vorwarnung entfernt werden können. Der Legacy-Test hält dazu intern gepflegte Listen veralteter Klassen, Methoden und Verzeichnisstrukturen vor und vergleicht den eigenen Code dagegen.

Ein klassisches Beispiel ist die direkte Nutzung des ObjectManagers innerhalb eines Konstruktors, statt die benötigte Abhängigkeit sauber per Dependency Injection zu übergeben. Dieses Muster funktioniert technisch weiterhin, gilt aber als Legacy-Pattern, weil es Tests erschwert, versteckte Abhängigkeiten erzeugt und dem gesamten Prinzip der Service Contracts widerspricht, auf dem Magento 2 aufbaut. Der Legacy-Test markiert solche Stellen zuverlässig, lange bevor sie in einem Code-Review manuell auffallen würden.


<?php
declare(strict_types=1);

namespace Mironsoft\SeoSuite\Model;

use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Api\ProductRepositoryInterface;

/**
 * Flagged by the Legacy test: direct ObjectManager usage inside a constructor.
 */
class MetaBuilderLegacy
{
    public function build(int $productId): string
    {
        // Legacy pattern: bypasses constructor injection entirely.
        $repository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
        $product = $repository->getById($productId);

        return (string) $product->getMetaTitle();
    }
}

/**
 * Fixed version: dependency is injected through the constructor.
 */
class MetaBuilderFixed
{
    /**
     * @param ProductRepositoryInterface $productRepository Injected repository, no ObjectManager needed.
     */
    public function __construct(
        private readonly ProductRepositoryInterface $productRepository
    ) {
    }

    public function build(int $productId): string
    {
        $product = $this->productRepository->getById($productId);

        return (string) $product->getMetaTitle();
    }
}

Neben dem ObjectManager-Missbrauch prüft der Legacy-Test unter anderem auf verbliebene Magento-1-Aufrufe wie Mage::, auf als veraltet markierte Klassenkonstanten und auf Verzeichnisstrukturen, die dem alten Modulsystem entstammen. Für Migrationsprojekte, die alten Custom-Code in eine neue Magento-2-Codebasis überführen, ist der Legacy-Test oft der erste harte Realitätscheck, der zeigt, wie viel tatsächlich modernisiert werden muss.

7. Aufbau von dev/tests/static/testsuite und Ausführung

Alle statischen Tests laufen technisch über eine ganz normale PHPUnit-Testsuite, die unter dev/tests/static/testsuite/Magento/Test/ liegt und über dev/tests/static/phpunit.xml.dist konfiguriert wird. Innerhalb dieses Verzeichnisses gibt es klar getrennte Unterordner: Php/ enthält unter anderem den LiveCodeTest, der intern den Magento Coding Standard über phpcs ausführt, Integrity/Dependency/ enthält den Dependency-Test, und Legacy/ enthält den Legacy-Test samt seiner Ausnahme- und Referenzlisten.

Wichtig für eigene Module ist, dass diese Tests standardmäßig über Whitelist- beziehungsweise Blacklist-Konfigurationsdateien gesteuert werden, welche Pfade überhaupt geprüft werden. Ein neu angelegtes Modul unter app/code/Mironsoft wird automatisch erfasst, solange es dem Standard-Namespace-Schema folgt, spezielle Ausschlüsse lassen sich aber über eigene Konfigurationsdateien im Testsuite-Verzeichnis ergänzen, etwa um generierten oder bewusst noch nicht migrierten Code temporär auszunehmen.

Ausgeführt werden die statischen Tests wie jede andere PHPUnit-Suite, nur mit einer eigenen Konfigurationsdatei als Ziel. Da ein voller Lauf über alle Unterordner mehrere Minuten dauern kann, filtert man in der täglichen Entwicklung meist gezielt nach einer einzelnen Testklasse, etwa nur nach dem Dependency-Test oder nur nach dem Legacy-Test, statt jedes Mal die komplette Suite zu starten.

8. Statische Tests in die CI-Pipeline einbinden

In einer CI-Pipeline lohnt sich eine zweistufige Strategie für statische Tests. Die erste Stufe prüft bei jedem Commit oder Merge-Request nur die geänderten Dateien mit einem schnellen phpcs-Lauf gegen den Magento Coding Standard, was in der Regel wenige Sekunden dauert und sofortiges Feedback liefert. Die zweite Stufe führt die vollständige Suite unter dev/tests/static/testsuite aus, inklusive Dependency-Test und Legacy-Test über die gesamte Codebasis, entweder vor jedem Merge in den Hauptbranch oder als nächtlicher Lauf.

Diese Aufteilung ist bewusst gewählt, weil der Dependency-Test naturgemäß den gesamten Modulgraphen betrachten muss, um verlässliche Aussagen zu treffen, während ein reiner Coding-Standard-Check pro Datei isoliert funktioniert. Eine Pipeline, die ausschließlich geänderte Dateien gegen den Dependency-Test prüft, würde Abhängigkeiten übersehen, die durch Änderungen an ganz anderer Stelle im Modul entstanden sind.

Entscheidend ist außerdem, dass die Pipeline bei einem Verstoß den Merge tatsächlich blockiert, nicht nur eine Warnung ausgibt. Statische Tests entfalten ihren vollen Wert nur als hartes Gate: ein Team, das Verstöße gegen den Magento Coding Standard oder Dependency-Test-Fehler als optionale Hinweise behandelt, sammelt über Monate genau die technischen Schulden an, die diese Tests eigentlich verhindern sollen.

9. Vergleich: Coding Standard, Dependency Test, Legacy Test und PHPStan

Alle drei Bausteine der statischen Tests prüfen unterschiedliche Aspekte derselben Codebasis und ergänzen sich, statt sich zu überschneiden. Der Magento Coding Standard sichert Konventionen und Stil, der Dependency-Test sichert die strukturelle Integrität des Modulsystems, und der Legacy-Test sichert die Zukunftsfähigkeit gegenüber kommenden Magento-Versionen. PHPStan wird an dieser Stelle nur zur Einordnung erwähnt: es prüft Typinformationen und Datenflüsse und ist damit ein eigenständiges Thema außerhalb dieses Artikels.

Prüfebene Werkzeug Prüft Ausführung
Coding Standard phpcs + Magento2-Ruleset Stil, Security- und Performance-Konventionen pro Datei Sekunden
Dependency Test dev/tests/static (PHPUnit) Undeklarierte Modulabhängigkeiten über module.xml Sekunden bis Minuten
Legacy Test dev/tests/static (PHPUnit) Veraltete Klassen, Methoden und Magento-1-Patterns Sekunden bis Minuten
PHPStan (Kontrast) PHPStan Typsicherheit und Datenflüsse, separates Thema Sekunden bis Minuten

Die praktische Konsequenz: keiner dieser statischen Tests ersetzt einen anderen. Ein Modul kann den Magento Coding Standard vollständig einhalten und trotzdem den Dependency-Test wegen einer fehlenden Sequenz reißen, oder umgekehrt strukturell sauber sein und dennoch Legacy-Patterns enthalten. Erst die Kombination aller drei Prüfungen aus dev/tests/static ergibt ein vollständiges Bild der strukturellen und konventionellen Qualität eines Magento-2-Moduls.

Mironsoft

Magento Coding Standard, Dependency-Checks und CI-Integration für Magento 2

Statische Tests von Anfang an sauber aufsetzen?

Wir richten den Magento Coding Standard, den Dependency-Test und den Legacy-Test für eure Module ein, von der phpcs.xml über korrekte module.xml-Sequenzen bis zur harten CI-Gate-Integration.

Coding Standard

phpcs.xml und Magento Coding Standard sauber konfigurieren

Dependency Test

module.xml-Sequenzen korrigieren und dauerhaft absichern

CI-Gate

Statische Tests als hartes Merge-Gate in eure Pipeline einbauen

10. Zusammenfassung

Statische Tests unter dev/tests/static decken in Magento 2 eine Lücke ab, die weder PHPStan noch PHPUnit schließen können: die Einhaltung des Magento Coding Standard, die strukturelle Integrität des Modulsystems über den Dependency-Test und die Zukunftsfähigkeit des Codes über den Legacy-Test. Wer diese drei Prüfungen konsequent in den Entwicklungsalltag integriert, statt sie nur einmalig vor einem Release laufen zu lassen, verhindert genau die Fehlerklasse, die sonst erst bei einer geänderten Modul-Reihenfolge oder einem Major-Update sichtbar wird.

Der entscheidende Hebel ist eine saubere phpcs.xml-Konfiguration für den Magento Coding Standard, gepflegte Sequenzen in jeder module.xml und ein CI-Gate, das Verstöße tatsächlich blockiert statt nur zu protokollieren. Kombiniert mit einer zweistufigen Pipeline, schneller Coding-Standard-Check pro Commit und vollständiger Dependency- und Legacy-Prüfung vor jedem Merge, werden statische Tests zu einem verlässlichen Fundament, auf dem sich weitere Qualitätssicherung wie PHPStan und PHPUnit erst sinnvoll aufbauen lässt.

Statische Tests in Magento 2: Das Wichtigste auf einen Blick

Magento Coding Standard

phpcs-Ruleset Magento2 aus magento/magento-coding-standard, prüft Stil, Security und Performance.

Dependency Test

Findet undeklarierte Modulabhängigkeiten gegenüber den Sequenzen in module.xml.

Legacy Test

Erkennt veraltete Patterns wie direkte ObjectManager-Aufrufe oder Magento-1-Reste.

CI-Betrieb

Schneller Coding-Standard-Check pro Commit, vollständige Suite als hartes Gate vor jedem Merge.

11. FAQ: Statische Tests in Magento 2

1Was sind statische Tests in Magento 2?
Magentos eigene Suite unter dev/tests/static: Magento Coding Standard per phpcs, Dependency-Test und Legacy-Test, alle ohne Codeausführung.
2Ist der Magento Coding Standard dasselbe wie PSR-12?
Nein, er ergänzt PSR-12-nahe Konventionen um Magento-spezifische Security-, Performance- und Architektur-Sniffs.
3Was prüft der Dependency-Test genau?
Er gleicht genutzte Fremdklassen mit den in module.xml deklarierten Sequenzen ab und meldet fehlende Deklarationen mit Datei und Zeile.
4Wofür ist der sequence-Block gut?
Er legt die Ladereihenfolge der Module fest und ist die Grundlage, gegen die der Dependency-Test prüft.
5Was flaggt der Legacy-Test?
Veraltete Muster wie direkte ObjectManager-Aufrufe oder Magento-1-Reste wie Mage:: Aufrufe.
6Ist das dasselbe wie PHPStan?
Nein, PHPStan prüft Typen und Datenflüsse als eigenständiges Werkzeug, unabhängig von den statischen Tests unter dev/tests/static.
7Prüfen statische Tests PHPUnit-Testcode?
Nein, sie prüfen keine Testlogik. PHPUnit-Tests schreiben und ausführen ist ein separates Thema.
8Wie führe ich den Magento Coding Standard lokal aus?
Mit phpcs --standard=Magento2 oder über eine phpcs.xml, die das Ruleset Magento2 referenziert.
9Wie schnell laufen statische Tests in der CI?
Ein Coding-Standard-Check auf geänderte Dateien dauert Sekunden, die volle Suite kann mehrere Minuten dauern.
10Sollten statische Tests einen Merge blockieren?
Ja, nur als hartes Gate verhindern sie dauerhaft technische Schulden statt nur Warnungen zu protokollieren.