EcomDev PHPUnit Tip #13

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #13: Speed up EAV fixtures

If you use the EAV fixtures to create products, categories and customers for tests, the fixture processor or EcomDev_PHPUnit makes a backup of the according tables before each test and restores it afterwards. Since the “magento_unit_tests” test database is copied from the current Magento database by default, this can result in much unnecessary overhead.

We can speed up tests that run several minutes many times over, by cleaning up the EAV tables in the test database. To do so, delete all rows in the entity main tables of the test DB, the associated attributes etc. are deleted automatically with triggers. With one exception: Do not delete the default root category:

delete from catalog_product_entity;
delete from catalog_category_entity where entity_id > 2;
delete from customer_entity;

Show random products in Magento: You are doing it wrong

Well, probably (I certainly have).

A typical requirement is to show random products on the home page, random products of a category on the category page, or similar. Magento offers a simple block that can show a specified number of random products, Mage_Catalog_Block_Product_List_Random. To add filtering, like by category, you still need to write your own block, but it could easily be extended.

This is the CMS code:

{{block type="catalog/product_list_random" num_products="4"}}

But don’t get too excited. This block type will make your site slow, you should not use it. The problem is in this line:

$collection->getSelect()->order('rand()');

I’ve been recently advocating against using ORDER BY RAND() to select random products from a Magento database. 1 Generally, this method is discouraged because of its performance issues.

Continue reading “Show random products in Magento: You are doing it wrong”

Notes:

  1. For example in this Magento StackExchange post: Four Random Products on Homepage

Efficiently Draw Random Elements From Large PHP Array

I recently needed to select few random elements from a big database table and was looking for alternatives to ORDER BY RAND() (because of its performance issues). Because the IDs are not continuous, the proposed solutions in the linked article are not sufficient.

So the idea is to fetch all IDs from the table, pick X random IDs and query these directly. The memory overhead of reading an array of about 100,000 IDs is not too big, so the problem is reduced to how to pick random IDs efficiently with PHP.
Continue reading “Efficiently Draw Random Elements From Large PHP Array”

EcomDev PHPUnit Tip #12

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #12: Cache

You can turn caches on and off per test, using the following annotations:

/**
 * @test
 * @cache on all
 */
public function testWithCache()

/**
 * @test
 * @cache off all
 */
public function testWithoutCache()

/**
 * @test
 * @cache off all
 * @cache on layout
 * @cache on block_html
 */
public function testWithSomeCachesTurnedOn()


/**
 * @test
 * @cache on all
 * @cache off translate
 * @cache off config
 */
public function testWithSomeCachesTurnedOff()

The annotations are executed in order, so if you use “all”, it should always come before all other @cache annotations.

EcomDev PHPUnit Tip #11

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #11: Minimal Fixtures For Product Types

Below are some example fixtures for different product types. They are as minimal as possible to create products and their relationships. In most cases you will want to add more attributes like name, visibility, status, price and stock status.

Continue reading “EcomDev PHPUnit Tip #11”

EcomDev PHPUnit Tip #10

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #10: Broken Translations

It happens that translations are not working in unit tests if you replace a helper with a mocked helper, using mockHelper() and replaceByMock(), especially in developer mode where translations only work if defined for the right module.

The module name is determined in Mage_Core_Helper_Abstract by $this->_getModuleName(), so let’s look into this method:

/**
 * 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;
}

If your helper class is Your_Awesome_Helper_Data, the mocked helper class will actually be Mock_Your_Awesome_Helper_Data. As a module named Mock_Your_Awesome doesn’t exist, nothing can be translated.

Solution

To make your helpers unit test proof (and as a side effect also rewrite proof), define _moduleName explicitly:

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

First described here: http://magento.stackexchange.com/questions/46255/ecomdev-phpunit-translation-not-working-with-mocked-helper

My Checklist For Magento Shop Analysis

For me, the first step before starting work on an unknown existing Magento installation,  is always a shop analysis. Not only to understand the requirements and processes of the merchant, but also and above all to evaluate the quality and recognize possible problems early on. Unfortunately, many shops are tinkered without any knowledge of conventions and best practices, which leads to:

  1. Suffering performance
  2. Security vulnerabilities
  3. Impossibility to update
  4. Unmaintainable code

Points 1 and 2 directly affect the merchant, point 3 and 4 lead to bugs being accumulated over time, which results in new changes causing bugs in different places, that get harder and harder to solve.

This bug-ridden cycle is often the reason merchants try a new agency or developer to “fix everything”, so it is no coincidence that most shops that land on my desk are in such a degenerated state. The more important it is to analyze first, before any quote for further development and maintenance, to not fall into the same trap as the predecessor and to add clarity on both sides.

Three-Tier Analysis

The analysis has a three-tier design such that after the first tier (1 to 2 hours of effort) there is already a rough estimation of quality and need for action, and after the second tier it is possible to make a quote for cleaning and updating the shop.

Read more at magenticians.com

Continue reading “My Checklist For Magento Shop Analysis”

EcomDev_PHPUnit Tip #9

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #9: Checkout Test

3 Years ago I already wrote an article about how to write a checkout integration test. But the practices used there are not longer up to date and some workarounds have become unnecessary. This post shows what’s necessary to write a test that simulates the Magento checkout, using techniques learned in Tip #1.

  1. Since there are some singletons involved, make sure to reset their state:
    /*
     * @test
     * @singleton checkout/session
     * @singleton customer/session
     * @singleton checkout/cart
     */
    
  2. You should visit the cart page once to trigger totals collection. Assuming, the customer id is 1 and she has an active quote (from previously added products in the test or from a quote fixture), you start with:
    $this->customerSession(1);
    $this->dispatch('checkout/cart');
    
  3. Before each new request, you have to reset the checkout session singleton manually within the test, otherwise the quote is not reloaded and you might even lose it completely:
    Mage::unregister('_singleton/checkout/session');
    $this->customerSession(1);
    $this->dispatch('checkout/onepage');
  4. Sometimes you want to logout a customer with active shopping cart. This needs three steps:
    Mage::getSingleton('customer/session')->logout();
    Mage::getSingleton('checkout/cart')->unsetData();
    $this->guestSession();

EcomDev_PHPUnit Tip #8

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #8: “sort_order is ambiguous” Error

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

If you get this MySQL error in your tests, you probably forgot the attribute set in your EAV fixtures. attribute_set_id should always be set, even for customers and addresses (just use “0” there)

EcomDev_PHPUnit Tip #7

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #7: YAML Directory Fallback

YAML files for fixtures, expectations and data providers are expected in the following scheme by default, where in this example Importer.php contains the test case and testImport is the test method:

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

Here the signature with annotations:

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

Instead of the method name the file name of the YAML file can also be stated explicitly, like in this example where another test also uses fixtures/testImport.yaml (how this also works for data providers, see Tip #3).

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

But even the directory structure doesn’t have to be used in this form. If EcomDev_PHPUnit doesn’t find the according file in this place, the following fallback hierarchy is used:

  1. Default: see above
  2. Module: The directories “fixtures”, “expectations” and “providers” are searched directly in the “Test” directory of the module
  3. Global: The directories are searched in any parent directory of the test case up until the Magento root

This way you can use fixtures etc. across multiple test cases. Today I prefer the second variant for module wide definition in most cases, at least for fixtures.

Incidentally, the hierarchy is defined in the EcomDev_PHPUnit config.xml as follows:

<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>

These loaders are models which inherit from EcomDev_PHPUnit_Model_Yaml_AbstractLoader, so with an own module you theoretically can add own custom loaders to use arbitrary sources for the YAML files.