EcomDev_PHPUnit Tipp #11

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #11: Minimal-Fixtures für Produkttypen

Es folgen einige Beispiel Fixtures für verschiedene Produkttypen. Sie sind so minimal gehalten, dass die Daten gerade ausreichen, um die Produkte und ggf. Produktbeziehungen anzulegen. In den meisten Fällen wird man zusätzliche Attribute wie Name, Status, Sichtbarkeit, Preis und Lagerbestand benötigen.

Continue reading “EcomDev_PHPUnit Tipp #11”

EcomDev_PHPUnit Tipp #10

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #10: Defekte Übersetzungen

Es kann passieren, dass Übersetzungen in Unit Tests nicht funktionieren, wenn man einen Helper mit einem Helper-Mock ersetzt (mit mockHelper() und replaceByMock()), insbesondere im Developer Mode, wo Übersetzungen nur funktionieren wenn sie für das richtige Modul definiert wurden.

Der Modulname wird in Mage_Core_Helper_Abstract mit $this->_getModuleName() ermittelt, also schauen wir uns die Methode einmal an:

/**
 * Retrieve helper module name
 *
 * @return string
 */
protected function _getModuleName()
{
    if (!$this->_moduleName) {
        $class = get_class($this);
        $this->_moduleName = substr($class, 0, strpos($class, '_Helper'));
    }
    return $this->_moduleName;
}

Wenn Deine Helper Klasse Your_Awesome_Helper_Data ist, wird die Klasse des Helper-Mocks Mock_Your_Awesome_Helper_Data heißen. Weil ein Modul namens Mock_Your_Awesome nicht existiert, gibt es keine Übersetzungen.

Lösung

Um Helper Unit-Test-sicher zu machen (und als Nebeneffekt auch Rewrite-sicher), definiere _moduleName explizit:

class Your_Awesome_Helper_Data extends Mage_Core_Helper_Abstract
{
    protected $_moduleName = 'Your_Awesome';
}

Zuerst beschrieben hier: http://magento.stackexchange.com/questions/46255/ecomdev-phpunit-translation-not-working-with-mocked-helper

Meine Checkliste bei der Analyse von Magento Shops

Der erste Schritt vor der Arbeit an einer bestehenden und mir unbekannten Magento Installation, ist bei mir immer eine Shop Analyse, nicht nur um die Anforderungen und Prozesse des Händlers besser zu verstehen, sondern auch und vor allen Dingen, um die Qualität einzuschätzen und mögliche Probleme frühzeitig zu erkennen. Denn leider sind viele Shops ohne Kenntnis von Konventionen und Best Practices “zusammengebastelt”, was dazu führt, dass:

  1. Die Performance leidet
  2. Sicherheitslücken entstehen
  3. Updates unmöglich werden
  4. Der Code nicht wartbar ist

Punkt 1 und 2 können den Händler ganz direkt schmerzhaft treffen, Punkt 3 und 4 führen mit der Zeit dazu, dass Bugs sich anhäufen, neue Änderungen häufig zu neuen Fehlern an anderer Stelle führen, die immer schwerer zu lösen sind.

Das ist dann häufig der Punkt, an dem eine neue Agentur oder ein neuer Entwickler gesucht wird, also ist es kein Zufall, dass Shops meist in solch degeneriertem Zustand auf meinem Tisch landen. Umso wichtiger ist die Analyse als erster Schritt, noch vor einem Angebot für Weiterentwicklungen, um nicht blind in die selbe Falle zu laufen wie die Vorgänger, und von Vorneherein für Klarheit auf beiden Seiten zu sorgen.

Continue reading “Meine Checkliste bei der Analyse von Magento Shops”

EcomDev_PHPUnit Tipp #9

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #9: Checkout Test

Vor 3 Jahren habe ich schonmal einen Artikel dazu geschrieben, wie man einen Integrationstest für den Checkout schreibt. Aber die Praktiken, die ich dort angewendet habe sind heute nicht mehr aktuell und einige der Workarounds sind nicht mehr notwendig. Dieser Beitrag zeigt, was notwendig ist um einen Test zu schreiben, der den Magento Checkout simuliert und nutzt dabei die in Tipp #1 gelernte Technik.

  1. Da einige Singletons involviert sind, stelle sicher, dass ihr Status zurückgesetzt wird:
    /*
     * @test
     * @singleton checkout/session
     * @singleton customer/session
     * @singleton checkout/cart
     */
    
  2. Es ist ratsam, als erstes den Warenkorb zu besuchen, um die Totals Collection zu triggern. Angenommen, der Kunde hat die ID 1 und einen aktiven Warenkorb (von zuvor im Test in den Warenkorb gelegten Produkten oder einer Quote Fixture), dann beginnen wir mit:
    $this->customerSession(1);
    $this->dispatch('checkout/cart');
    
  3. Vor jedem neuen Request, muss das Checkout Session Singleton manuell während des Tests zurückgesetzt werden, sonst wird die Quote nicht neu geladen und kann unter Umständen ganz verloren gehen:
    Mage::unregister('_singleton/checkout/session');
    $this->customerSession(1);
    $this->dispatch('checkout/onepage');
  4. Manchmal möchte man einen Kunden mit aktivem Warenkorb ausloggen. Dazu sind drei Schritte notwendig:
    Mage::getSingleton('customer/session')->logout();
    Mage::getSingleton('checkout/cart')->unsetData();
    $this->guestSession();

EcomDev_PHPUnit Tipp #8

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #8: “sort_order is ambiguous” Fehler

Zend_Db_Statement_Exception: SQLSTATE[23000]: Integrity constraint violation: 1052 Column ‘sort_order’ in order clause is ambiguous

Wer bei seinen Tests diesen Fehler von MySQL bekommt, hat wahrscheinlich bei EAV Fixtures das Attribut-Set vergessen. attribute_set_id sollte immer gesetzt sein, auch bei Kunden und Adressen (dort einfach “0” eintragen)

EcomDev_PHPUnit Tipp #7

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #7: YAML Verzeichnis-Fallback

YAML-Dateien für Fixtures, Expectations und Data Providers werden standardmäßig in folgendem Schema erwartet, wobei in diesem Beispiel Importer.php den Test Case enthält und testImport die Test-Methode ist:

│   Importer.php
│
├───Importer
│   ├───expectations
│   │       testImport.yaml
│   │
│   ├───fixtures
│   │       testImport.yaml
│   │
│   └───providers
│           testImport.yaml

Hier die Signatur mit Annotations:

    /**
     * @param string $csv CSV file content
     * @test
     * @loadFixture
     * @loadExpectation
     * @dataProvider dataProvider
     */
    public function testImport($csv)

Anstelle des Methodennamens kann der Dateiname der YAML-Datei jeweils explizit angegeben werden, hier z.B. in einem weiteren Test, der auch fixtures/testImport.yaml benutzt (wie das auch für Data Provider funktioniert, siehe Tipp #3).

    /**
     * @param string $csv CSV file content
     * @test
     * @loadFixture testImport
     * @dataProvider dataProvider
     * @expectedException RuntimeException
     */
    public function testFailingImport($csv)

Aber auch die Verzeichnisstruktur muss nicht in dieser Form genutzt werden. Findet EcomDev_PHPUnit die jeweilige Datei nämlich nicht an der Stelle, greift folgende Fallback-Hierarchie:

  1. Default: siehe oben
  2. Module: Die Verzeichnisse “fixtures”, “expectations” und “providers” werden direkt im “Test” Verzeichnis des Moduls gesucht
  3. Global: Die Verzeichnisse werden in sämtlichen Oberverzeichnissen des Test Cases bis hin zum Magento Root gesucht

Auf diese Weise lassen sich Fixtures etc. Test Case übergreifend verwenden. Ich bevorzuge mittlerweile zumindest für Fixtures in den meisten Fällen die zweite Variante für modulweite Definition.

Die Hierarchie ist übrigens in der config.xml von EcomDev_PHPUnit wie folgt definiert:

<config>
    <phpunit>
        <suite>
            <yaml>
                <model>ecomdev_phpunit/yaml_loader</model>
                <loaders>
                    <default>ecomdev_phpunit/yaml_loader_default</default>
                    <module>ecomdev_phpunit/yaml_loader_module</module>
                    <global>ecomdev_phpunit/yaml_loader_global</global>
                </loaders>
            </yaml>
        </suite>
    </phpunit>
</config>

Diese Loaders sind Models, die von EcomDev_PHPUnit_Model_Yaml_AbstractLoader erben, mit einem eigenen Modul lassen sich hier also theoretisch weitere, eigene Loader hinzufügen und somit beliebige Quellen für die YAML Dateien verwenden.

EcomDev_PHPUnit Tipp #6

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #6: Fixture rollback

Wenn Du Fixtures nutzt, werden die Fixture-Daten aus der Datenbank entfernt, wenn der jeweilige Test durchgelaufen ist. Was dabei zu beachten ist, ist dass es sich hier nicht um einen Transaktions-Rollback handelt, stattdessen werden alle Tabellen die von der Fixture betroffen waren, geleert (truncated).

Das hat einige Implikationen:

  • Man sollte niemals table fixtures mit Tabellen benutzen, die wichtige Core-Daten enthalten, wie z.B. core_config_data oder eav_attribute
  • Daten in Tabellen, die nicht in der Fixture stehen, bleiben bestehen. Man kann das Löschen von Daten, die während dem Test erstellt wurden, mit einer leern Fixture triggern, zum Beispiel für Quotes und Orders:
    tables:
      sales/quote: []
      sales/order: []
    
  • Wenn dieses Verhalten nicht erwünscht ist, sollte man hinter sich selbst aufräumen, also erstellte Daten in einer tearDown Methode löschen.
  • Verlass Dich nicht zu sehr auf Daten, die in der Original-Datenbank vorhanden sind, denn andere Tests können sie mit ihren Fixtures überschreiben und schließlich löschen.
  • Wenn Du shared fixtures mit @loadSharedFixture nutzt, werden alle Daten in den shared fixtures nur einmal erstellt und entfernt. Alle Tabellen, die von einer shared fixture betroffen sind, werden zwischendurch nicht aufgeräumft, auch wenn andere normale Fixtures Daten hinzufügen. Mein Rat: Shared Fixtures nur mit Bedacht einsetzen, besser gar nicht

EcomDev_PHPUnit Tipp #5

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #5: Secure Area

Problem: Testfälle, die EcomDev_PhpUnit_Test_Case_Controller extenden und eine customer Fixture nutzen, schlagen mit Cannot complete this operation from non-admin area bzw. Diese Aktion kann nicht außerhalb des Admin-Bereichs fertiggestellt werden. fehl weil Magento beim tearDown im area=frontend Modus ist und kein Löschen von Kunden erlaubt. Das selbe Problem tritt auf, wenn man versucht, Kunden im Test zu löschen, ohne im adminhtml Kontext zu sein.

Das Customer Model prüft, ob das isSecureArea Flag in der Magento Registry, um das Problem zu lösen, setzen wir den Flag also im Test. Es gibt zwei mögliche Wege dies zu tun:

1.) Wenn Du Kunden im Test selbst erstellst und löschst:

/*
 * @test
 * @registry isSecureArea
 */
public function testThatNeedsToDeleteCustomers()
{
    Mage::register('isSecureArea', true);
    // ...
}

(Beachte, dass die @registry Annotation den Flag nachher zurücksetzt, siehe Tipp #1)

2.) Wenn Du eine Fixture mit Kunden nutzt:

protected function setUp()
{
    Mage::register('isSecureArea', true);
    parent::setUp();
}
protected function tearDown()
{
    parent::tearDown();
    Mage::unregister('isSecureArea');
}

oder bei Fixtures auf Klassen-Ebene:

    public static function setUpBeforeClass()
    {
        Mage::register('isSecureArea', true);
    }
    public static function tearDownAfterClass()
    {
        Mage::unregister('isSecureArea');
    }

EcomDev_PHPUnit Tipp #4

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #4: Benannte Parameter

Der Vorteil von YAML-Dateien für die Konfiguration soll einfache Lesbarkeit sein. Wenn aber ein Data Provider so aussieht, ist es nicht weit her mit der Lesbarkeit:

-
  - 7
  - 1
  -
    5: 7
    6: 9
-
  - 7
  - 1
  -
    5: 8
    6: 10

Technisch das selbe, aber deutlich besser wartbar:

Bundle_X_A1_B2:
  product_id: 7
  qty: 1
  bundle_selections_by_option:
    5: 7
    6: 9
Bundle_X_A2_B2:
  product_id: 7
  qty: 1
  bundle_selections_by_option:
    5: 8
    6: 10

Dass es hier um das in den Warenkorb legen von Bündelprodukten geht, kann man zumindest erahnen und wenn man es weiß, sind die Testdaten einfach zu verstehen, auch ohne in den Quelltext zu sehen.

Ein weiterer positiver Effekt, ist dass PHPUnit bei fehlgeschlagenen Tests nicht mehr TestCase::test() with data set #1 ausgibt, sondern z.B. TestCase::test() with data set "Bundle_X_A1_B2"