Using of TDD practices for Magento

Preview:

DESCRIPTION

My Presentation from MeetMagento NL 2011

Citation preview

Using of Test Driven DevelopmentPractices for Magento

Ivan ChepurnyiMagento Trainer / Lead Developer

Ivan Chepurnyi Meet Magento Netherlands

Short Overview

Write a test

Run the tests

Fail

Pass

Write the code

Run the tests

Fail

Pass

1. Imaging how your feature should work and write a failing test

2. Write feature quickly for receiving passed test

3. Refactor your code and run the test again

Ivan Chepurnyi Meet Magento Netherlands

Conventional Development

• You need to write a full functionality for seeing the result• You think that your code works• Debugging• With every fixed issue you may produce a new one• Even if you write unit tests after implementation it doesn’t

guarantee that you detect the defect.

Ivan Chepurnyi Meet Magento Netherlands

Test Driven Development

• You don’t need to implement full functionality for seeing the result

• Your colleagues can use your test as learning material• The test proofs that your code works and can be verified• Serious defects can be fixed on early state

Ivan Chepurnyi Meet Magento Netherlands

Defect Costs

• Defect found at the coding stage

• Defect found during QA phase

• Defect found after going live

Percentage from project development hours

< 1%

20%

> 40%

Ivan Chepurnyi Meet Magento Netherlands

Type of Tests

• Automated Test• Regression Test• Learning Test• Integration Test

Ivan Chepurnyi Meet Magento Netherlands

• Test in Isolation

• Make test simple

• Test erroneous situations

• Test Doubles (Fake heavy resources you don’t depend on)

Main Principles

Ivan Chepurnyi Meet Magento Netherlands

EcomDev_PHPUnit

• Making possible isolation of your test

• Easy test data loading via Yaml fixtures

• Data providers and expectations for reusable tests

• Easy way of testing configuration files

• Easy way for Layouts & Controllers integration test

Ivan Chepurnyi Meet Magento Netherlands

Simple Test Case

class EcomDev_Example_Test_Model_Product extends EcomDev_PHPUnit_Test_Case

{/** * @test * @loadFixture * @dataProvider dataProvider */ public function priceCalculation($productId, $storeId) { $storeId = Mage::app()->getStore($storeId)->getId(); $product = Mage::getModel('catalog/product') ->setStoreId($storeId) ->load($productId);

$expected = $this->expected('%s-%s', $productId, $storeId); $this->assertEquals($expected->getFinalPrice(), $product->getFinalPrice());

$this->assertEquals($expected->getPrice(), $product->getPrice()); }}

Test Case Class

Ivan Chepurnyi Meet Magento Netherlands

Simple Test Case

eav:    catalog_product:     - entity_id: 1        type_id: simple       sku: book          website_ids:          - usa_website          - canada_website          price: 12.99        status: 1  # Enabled        visibility: 4  # Visible in Catalog & Search        /websites:  # Set different prices per website         usa_website:            special_price: 9.99          german_website:            price: 9.99            special_price: 5.99

Yaml Fixture

Ivan Chepurnyi Meet Magento Netherlands

Simple Test Case

1-2: # Product=Book Store=USA

final_price: 9.99

price: 12.99

1-3: # Product=Book Store=Canada

final_price: 12.99

price: 12.99

Yaml Expectation

- - 1 - usa- - 1 - canada- - 1 - germany

Yaml Data Provider

Ivan Chepurnyi Meet Magento Netherlands

Event Dispatch Check

class EcomDev_Example_Test_Model_Cms_Page extends EcomDev_PHPUnit_Test_Case

{

// …

public function testAvailableStatuses() {

Mage::getModel(‘cms/page’)->getAvailableStatuses();

$this->assertEventDispatched(

‘cms_page_get_available_statuses’

);

}

}

Test Case

Ivan Chepurnyi Meet Magento Netherlands

Test Doubles

class EcomDev_PHPUnit_Tes_Case_Controllerextends EcomDev_PHPUnit_Test_Case

{protected function registerCookieStub()

{ $cookie = $this->getModelMock('core/cookie', array('set', 'delete')); $cookie->expects($this->any()) ->method('set') ->will($this->returnCallback(array($this, 'setCookieCallback‘)));

$cookie->expects($this->any()) ->method('delete‘) ->will($this->returnCallback(array($this, 'deleteCookieCallback‘)));

$this->replaceByMock('singleton', 'core/cookie', $cookie); return $this; }

}

Test Case

Ivan Chepurnyi Meet Magento Netherlands

Config Test Case

class EcomDev_Example_Test_Config_Main extends EcomDev_PHPUnit_Test_Case_Config

{

//….

public function testModuleVersion()

{

$this->assertModuleCodePool('local');

$this->assertModuleDepends(‘Mage_Catalog’);

$this->assertModuleVersionGreaterThan(‘0.1.0');

}

}

Testing Module Nodes

Ivan Chepurnyi Meet Magento Netherlands

Config Test Case

class EcomDev_Example_Test_Config_Main extends EcomDev_PHPUnit_Test_Case_Config

{

//….

public function testClassAliasDefinitions()

{

$this->assertModelAlias('catalog/product', 'Mage_Catalog_Model_Product');

$this->assertResourceModelAlias(

'catalog/product',

‘Mage_Catalog_Model_Resource_Eav_Mysql4_Product‘

);

$this->assertBlockAlias(

'catalog/product_list',

'Mage_Catalog_Block_Product_List‘

);

}

}

Testing Class Aliases

Ivan Chepurnyi Meet Magento Netherlands

Config Test Case

class EcomDev_Example_Test_Config_Main extends EcomDev_PHPUnit_Test_Case_Config

{

//….

public function testEventObservers()

{

$this->assertEventObserverDefined(

'frontend', 'customer_login',

'catalog/product_compare_item',

'bindCustomerLogin'

);

}

}

Testing Event Observer Definitions

Ivan Chepurnyi Meet Magento Netherlands

Controller Test Case

class EcomDev_Example_Test_Controller_Main extends EcomDev_PHPUnit_Test_Case_Controller

{

public function testRequest()

{

$this->dispatch('cms');

$this->assertRequestDispatched();

$this->assertRequestNotForwarded();

$this->assertRequestRoute('cms/index/index');

$this->assertRequestRouteName('cms');

$this->assertRequestControllerName('index');

$this->assertRequestControllerModule('Mage_Cms');

$this->assertRequestActionName('index');

}

}

Testing Request

Ivan Chepurnyi Meet Magento Netherlands

Controller Test Case

class EcomDev_Example_Test_Controller_Main

extends EcomDev_PHPUnit_Test_Case_Controller

{

public function testLayout()

{

$this->dispatch('');

$this->assertLayoutHandleLoaded('cms_index_index');

$this->assertLayoutBlockCreated('right');

$this->assertLayoutBlockRendered('content');

$this->assertLayoutBlockActionNotInvoked(

'footer_links', 'addLink', '', array('Custom Title')

);

$this->assertLayoutBlockActionInvokedAtLeast(

'footer_links', 'addLink', 4, '‘

);

}

}

Testing Layouts

Ivan Chepurnyi Meet Magento Netherlands

What’s Next

• Write automated tests for your moduleshttp://www.magentocommerce.com/magento-connect/Ecommerce%20Developers/extension/5717/ecomdev_phpunit

• Keep project healthy during its lifecycle with Continuous Integration

• Running Daily Builds• Running Unit Tests in 10 minutes after last commit

– Hudson http://hudson-ci.org– phpUnderControl http://phpundercontrol.org/

Questions?

ivan.chepurnyi@ecomdev.org

Recommended