Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Quality Assurance in Magento

Quality Assurance in Magento

MageTest unit and functional testing module for use with Magento. Delivered at Magento Imagine 2011

Alistair Stead

October 08, 2011
Tweet

More Decks by Alistair Stead

Other Decks in Programming

Transcript

  1. QUALITY ASSURANCE IN
    MAGENTO EXTENSIONS
    Automated Functional Testing
    Wednesday, 9 February 2011

    View Slide

  2. WHO AM I
    • Alistair Stead
    • Technical Team Lead @ Ibuildings UK / Session Digital
    • Lead Magento projects for 3663, WMI, Kookai and others
    • Over 3 years experience with Magento
    • Zend Certified Engineer
    • Over 11 years commercial experience developing in PHP
    Wednesday, 9 February 2011

    View Slide

  3. CREATING SOFTWARE IS
    COMPLEX
    Wednesday, 9 February 2011

    View Slide

  4. CREATING SOFTWARE IS
    COMPLEX
    (Maintaining it is arguably harder)
    Wednesday, 9 February 2011

    View Slide

  5. FAULT TOLERANCE IS
    LOW
    Wednesday, 9 February 2011

    View Slide

  6. “Hello, technical support?”
    “I would like
    to report a bug...”
    Wednesday, 9 February 2011

    View Slide

  7. E-COMMERCE HAS A LOW
    TOLERANCE TO FAULTS.
    Wednesday, 9 February 2011

    View Slide

  8. MAGENTO MAKES IT
    OBVIOUS SOMETHING IS
    WRONG
    Wednesday, 9 February 2011

    View Slide

  9. With whom should
    the BUCK stop?
    Wednesday, 9 February 2011

    View Slide

  10. With whom should
    the BUCK stop?
    Lets make sure its not us....
    Wednesday, 9 February 2011

    View Slide

  11. SO WHAT CAN WE DO TO
    FIX THIS?
    Wednesday, 9 February 2011

    View Slide

  12. We are clever
    people right?
    Wednesday, 9 February 2011

    View Slide

  13. Just get more of us?
    Wednesday, 9 February 2011

    View Slide

  14. Testing at the eleventh hour...
    Wednesday, 9 February 2011

    View Slide

  15. We can use
    technology
    to help!
    Wednesday, 9 February 2011

    View Slide

  16. AUTOMATED TESTING
    FRAMEWORKS CAN DO THE
    HEAVY LIFTING.
    Selenium Watir
    Wednesday, 9 February 2011

    View Slide

  17. PHPUNIT
    The de-facto standard for unit testing in PHP projects
    Wednesday, 9 February 2011

    View Slide

  18. WE CAN RUN UNIT TESTS
    FOR MAGENTO!
    Wednesday, 9 February 2011

    View Slide

  19. /**
    * Magento PHPUnit TestCase
    *
    * @package Ibuildings_Mage_Test_PHPUnit
    * @copyright Copyright (c) 2011 Ibuildings. (http://www.ibuildings.com)
    * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
    * @author Alistair Stead
    * @version $Id$
    */
    /**
    * PHPUnit_Framework_Magento_TestCase
    *
    * @category Mage_Test
    * @package Ibuildings_Mage_Test_PHPUnit
    * @subpackage Ibuildings_Mage_Test_PHPUnit_TestCase
    * @uses PHPUnit_Framework_TestCase
    */
    abstract class Ibuildings_Mage_Test_PHPUnit_TestCase extends PHPUnit_Framework_TestCase {
    public function setUp() {
    parent::setUp();
    // Initialise Magento
    Mage::app();
    }
    }
    Wednesday, 9 February 2011

    View Slide

  20. /**
    * Magento PHPUnit TestCase
    *
    * @package Ibuildings_Mage_Test_PHPUnit
    * @copyright Copyright (c) 2011 Ibuildings. (http://www.ibuildings.com)
    * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
    * @author Alistair Stead
    * @version $Id$
    */
    /**
    * PHPUnit_Framework_Magento_TestCase
    *
    * @category Mage_Test
    * @package Ibuildings_Mage_Test_PHPUnit
    * @subpackage Ibuildings_Mage_Test_PHPUnit_TestCase
    * @uses PHPUnit_Framework_TestCase
    */
    abstract class Ibuildings_Mage_Test_PHPUnit_TestCase extends PHPUnit_Framework_TestCase {
    public function setUp() {
    parent::setUp();
    // Initialise Magento
    Mage::app();
    }
    }
    Wednesday, 9 February 2011

    View Slide

  21. MODELS
    Wednesday, 9 February 2011

    View Slide

  22. class DataCash_Dpg_Model_ConfigTest extends Ibuildings_Mage_Test_PHPUnit_TestCase
    {
    /**
    * Member variable to hold reference to the opbject under test
    *
    * @var DataCash_Dpg_Model_Config
    **/
    protected $_object;
    /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_object = Mage::getSingleton('dpg/config');
    }
    Wednesday, 9 February 2011

    View Slide

  23. class DataCash_Dpg_Model_ConfigTest extends Ibuildings_Mage_Test_PHPUnit_TestCase
    {
    /**
    * Member variable to hold reference to the opbject under test
    *
    * @var DataCash_Dpg_Model_Config
    **/
    protected $_object;
    /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_object = Mage::getSingleton('dpg/config');
    }
    Wednesday, 9 February 2011

    View Slide

  24. class DataCash_Dpg_Model_ConfigTest extends Ibuildings_Mage_Test_PHPUnit_TestCase
    {
    /**
    * Member variable to hold reference to the opbject under test
    *
    * @var DataCash_Dpg_Model_Config
    **/
    protected $_object;
    /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_object = Mage::getSingleton('dpg/config');
    }
    Wednesday, 9 February 2011

    View Slide

  25. /**
    * mageSingletonFactoryReturnsExpectedObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsExpectedObject()
    {
    $this->assertInstanceOf('DataCash_Dpg_Model_Config', $this->_object, 'Unexpected object
    returned by factory');
    } // mageSingletonFactoryReturnsExpectedObject
    /**
    * mageSingletonFactoryReturnsTheSameObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsTheSameObject()
    {
    $this->assertSame($this->_object, Mage::getSingleton('dpg/config'), 'Two different objects
    have been created');
    } // mageSingletonFactoryReturnsTheSameObject
    Wednesday, 9 February 2011

    View Slide

  26. /**
    * mageSingletonFactoryReturnsExpectedObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsExpectedObject()
    {
    $this->assertInstanceOf('DataCash_Dpg_Model_Config', $this->_object, 'Unexpected object
    returned by factory');
    } // mageSingletonFactoryReturnsExpectedObject
    /**
    * mageSingletonFactoryReturnsTheSameObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsTheSameObject()
    {
    $this->assertSame($this->_object, Mage::getSingleton('dpg/config'), 'Two different objects
    have been created');
    } // mageSingletonFactoryReturnsTheSameObject
    Wednesday, 9 February 2011

    View Slide

  27. /**
    * mageSingletonFactoryReturnsExpectedObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsExpectedObject()
    {
    $this->assertInstanceOf('DataCash_Dpg_Model_Config', $this->_object, 'Unexpected object
    returned by factory');
    } // mageSingletonFactoryReturnsExpectedObject
    /**
    * mageSingletonFactoryReturnsTheSameObject
    * @author Alistair Stead
    * @test
    */
    public function mageSingletonFactoryReturnsTheSameObject()
    {
    $this->assertSame($this->_object, Mage::getSingleton('dpg/config'), 'Two different objects
    have been created');
    } // mageSingletonFactoryReturnsTheSameObject
    Wednesday, 9 February 2011

    View Slide

  28. /**
    * getTransactionTypesReturnsArray
    * @author Alistair Stead
    * @test
    */
    public function getTransactionTypesReturnsArray()
    {
    $result = $this->_object->getTansactionTypes();
    $this->assertTrue(is_array($result), 'No array returned');
    $this->assertEquals(count($result), 2, 'More transaction types than expected');
    $this->assertArrayHasKey( 'A', $result, 'The array does not contain A' );
    $this->assertArrayHasKey( 'P', $result, 'The array does not contain P' );
    } // getTransactionTypesReturnsArray
    Wednesday, 9 February 2011

    View Slide

  29. /**
    * getTransactionTypesReturnsArray
    * @author Alistair Stead
    * @test
    */
    public function getTransactionTypesReturnsArray()
    {
    $result = $this->_object->getTansactionTypes();
    $this->assertTrue(is_array($result), 'No array returned');
    $this->assertEquals(count($result), 2, 'More transaction types than expected');
    $this->assertArrayHasKey( 'A', $result, 'The array does not contain A' );
    $this->assertArrayHasKey( 'P', $result, 'The array does not contain P' );
    } // getTransactionTypesReturnsArray
    Wednesday, 9 February 2011

    View Slide

  30. HELPERS
    Wednesday, 9 February 2011

    View Slide

  31. /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_helper = Mage::helper('contactsdepartment');
    $this->_departmentIds = array();
    $department = Mage::getModel('contactsdepartment/department');
    $fixtures = array(
    array(
    'department_name' => 'department one',
    'department_email' => '[email protected]',
    'store_id' => 0
    )
    );
    foreach ($fixtures as $data) {
    $department->addData($data);
    $department->save();
    $this->_departmentIds[] = $department->getId();
    }
    }
    Wednesday, 9 February 2011

    View Slide

  32. /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_helper = Mage::helper('contactsdepartment');
    $this->_departmentIds = array();
    $department = Mage::getModel('contactsdepartment/department');
    $fixtures = array(
    array(
    'department_name' => 'department one',
    'department_email' => '[email protected]',
    'store_id' => 0
    )
    );
    foreach ($fixtures as $data) {
    $department->addData($data);
    $department->save();
    $this->_departmentIds[] = $department->getId();
    }
    }
    Wednesday, 9 February 2011

    View Slide

  33. /**
    * Setup fixtures and dependencies
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setUp()
    {
    parent::setUp();
    $this->_helper = Mage::helper('contactsdepartment');
    $this->_departmentIds = array();
    $department = Mage::getModel('contactsdepartment/department');
    $fixtures = array(
    array(
    'department_name' => 'department one',
    'department_email' => '[email protected]',
    'store_id' => 0
    )
    );
    foreach ($fixtures as $data) {
    $department->addData($data);
    $department->save();
    $this->_departmentIds[] = $department->getId();
    }
    }
    Wednesday, 9 February 2011

    View Slide

  34. /**
    * helperFactoryReturnsTheExpectedClass
    * @author Alistair Stead
    * @test
    */
    public function helperFactoryReturnsTheExpectedClass()
    {
    $this->assertInstanceOf('Ibuildings_ContactsDepartment_Helper_Data', $this->_helper);
    } // helperFactoryReturnsTheExpectedClass
    /**
    * getDepartmentOptionsShouldReturnAssociativeArray
    * @author Alistair Stead
    * @test
    */
    public function getDepartmentOptionsShouldReturnAssociativeArray()
    {
    foreach ($this->_helper->getDepartmentOptions() as $option) {
    $this->assertArrayHasKey( 'value', $option, 'The options array does not have a value' );
    $this->assertArrayHasKey( 'label', $option, 'The options array does not have a label' );
    }
    } // getDepartmentOptionsShouldReturnAssociativeArray
    Wednesday, 9 February 2011

    View Slide

  35. /**
    * helperFactoryReturnsTheExpectedClass
    * @author Alistair Stead
    * @test
    */
    public function helperFactoryReturnsTheExpectedClass()
    {
    $this->assertInstanceOf('Ibuildings_ContactsDepartment_Helper_Data', $this->_helper);
    } // helperFactoryReturnsTheExpectedClass
    /**
    * getDepartmentOptionsShouldReturnAssociativeArray
    * @author Alistair Stead
    * @test
    */
    public function getDepartmentOptionsShouldReturnAssociativeArray()
    {
    foreach ($this->_helper->getDepartmentOptions() as $option) {
    $this->assertArrayHasKey( 'value', $option, 'The options array does not have a value' );
    $this->assertArrayHasKey( 'label', $option, 'The options array does not have a label' );
    }
    } // getDepartmentOptionsShouldReturnAssociativeArray
    Wednesday, 9 February 2011

    View Slide

  36. /**
    * helperFactoryReturnsTheExpectedClass
    * @author Alistair Stead
    * @test
    */
    public function helperFactoryReturnsTheExpectedClass()
    {
    $this->assertInstanceOf('Ibuildings_ContactsDepartment_Helper_Data', $this->_helper);
    } // helperFactoryReturnsTheExpectedClass
    /**
    * getDepartmentOptionsShouldReturnAssociativeArray
    * @author Alistair Stead
    * @test
    */
    public function getDepartmentOptionsShouldReturnAssociativeArray()
    {
    foreach ($this->_helper->getDepartmentOptions() as $option) {
    $this->assertArrayHasKey( 'value', $option, 'The options array does not have a value' );
    $this->assertArrayHasKey( 'label', $option, 'The options array does not have a label' );
    }
    } // getDepartmentOptionsShouldReturnAssociativeArray
    Wednesday, 9 February 2011

    View Slide

  37. Okay we’re done lets go home...
    Wednesday, 9 February 2011

    View Slide

  38. Hold on,
    we have not really
    done anything new.
    Wednesday, 9 February 2011

    View Slide

  39. DID WE MENTION
    FUNCTIONAL TESTING YET?
    Wednesday, 9 February 2011

    View Slide

  40. QUALITY ASSURANCE IN
    MAGENTO EXTENSIONS
    Automated Functional Testing
    Wednesday, 9 February 2011

    View Slide

  41. ZEND_TEST?
    Wednesday, 9 February 2011

    View Slide

  42. MAGENTO IS CLOSELY
    COUPLED TO
    MAGE_CORE_APPLICATION
    Wednesday, 9 February 2011

    View Slide

  43. Coupled components cause inflexibility
    Wednesday, 9 February 2011

    View Slide

  44. Dependency injection
    is the key
    Wednesday, 9 February 2011

    View Slide

  45. MAGE-TEST
    http://github.com/ibuildings/Mage-Test
    Wednesday, 9 February 2011

    View Slide

  46. Mage_Core_Model_App
    Ibuildings_Mage_Controller_Request_HttpTestCase
    Ibuildings_Mage_Controller_Response_HttpTestCase
    Ibuildings_Mage_Test_PHPUnit_ControllerTestCase
    Ibuildings_Mage_Test_PHPUnit_TestCase
    Wednesday, 9 February 2011

    View Slide

  47. MAGE_CORE_APPLICATION
    Loaded from the community code pool
    Wednesday, 9 February 2011

    View Slide

  48. /**
    * Provide a public method to allow the internal Request object
    * to be set at runtime. This can be used to inject a testing request object
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setRequest(Zend_Controller_Request_Abstract $request)
    {
    $this->_request = $request;
    }
    /**
    * Retrieve request object
    *
    * @return Mage_Core_Controller_Request_Http
    */
    public function getRequest()
    {
    if (empty($this->_request)) {
    $this->_request = new Mage_Core_Controller_Request_Http();
    }
    return $this->_request;
    }
    Wednesday, 9 February 2011

    View Slide

  49. /**
    * Provide a public method to allow the internal Request object
    * to be set at runtime. This can be used to inject a testing request object
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setRequest(Zend_Controller_Request_Abstract $request)
    {
    $this->_request = $request;
    }
    /**
    * Retrieve request object
    *
    * @return Mage_Core_Controller_Request_Http
    */
    public function getRequest()
    {
    if (empty($this->_request)) {
    $this->_request = new Mage_Core_Controller_Request_Http();
    }
    return $this->_request;
    }
    Wednesday, 9 February 2011

    View Slide

  50. /**
    * Provide a public method to allow the protected internal Response object
    * to be set at runtime. This can be used to inject a testing response object
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setResponse(Zend_Controller_Response_Abstract $response)
    {
    $this->_response = $response;
    }
    /**
    * Retrieve response object
    *
    * @return Zend_Controller_Response_Http
    */
    public function getResponse()
    {
    if (empty($this->_response)) {
    $this->_response = new Mage_Core_Controller_Response_Http();
    $this->_response->headersSentThrowsException = Mage::$headersSentThrowsException;
    $this->_response->setHeader("Content-Type", "text/html; charset=UTF-8");
    }
    return $this->_response;
    }
    Wednesday, 9 February 2011

    View Slide

  51. /**
    * Provide a public method to allow the protected internal Response object
    * to be set at runtime. This can be used to inject a testing response object
    *
    * @return void
    * @author Alistair Stead
    **/
    public function setResponse(Zend_Controller_Response_Abstract $response)
    {
    $this->_response = $response;
    }
    /**
    * Retrieve response object
    *
    * @return Zend_Controller_Response_Http
    */
    public function getResponse()
    {
    if (empty($this->_response)) {
    $this->_response = new Mage_Core_Controller_Response_Http();
    $this->_response->headersSentThrowsException = Mage::$headersSentThrowsException;
    $this->_response->setHeader("Content-Type", "text/html; charset=UTF-8");
    }
    return $this->_response;
    }
    Wednesday, 9 February 2011

    View Slide

  52. CONTROLLER TESTS
    Ibuildings_Mage_Test_PHPUnit_ControllerTestCase
    Wednesday, 9 February 2011

    View Slide

  53. /**
    * Bootstrap the Mage application in a similar way to the procedure
    * of index.php
    *
    * Then sets test case request and response objects in Mage_Core_App,
    * and disables returning the response.
    *
    * @return void
    * @author Alistair Stead
    */
    public function mageBootstrap()
    {
    Mage::reset();
    if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
    Mage::setIsDeveloperMode(true);
    }
    // Store or website code
    $this->mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    // Run store or run website
    $this->mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    // Initialize the Mage App and inject the testing request & response
    Mage::app($this->mageRunCode, $this->mageRunType, $this->options);
    Mage::app()->setRequest(new Ibuildings_Mage_Controller_Request_HttpTestCase);
    Mage::app()->setResponse(new Ibuildings_Mage_Controller_Response_HttpTestCase);
    }
    Wednesday, 9 February 2011

    View Slide

  54. /**
    * Bootstrap the Mage application in a similar way to the procedure
    * of index.php
    *
    * Then sets test case request and response objects in Mage_Core_App,
    * and disables returning the response.
    *
    * @return void
    * @author Alistair Stead
    */
    public function mageBootstrap()
    {
    Mage::reset();
    if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
    Mage::setIsDeveloperMode(true);
    }
    // Store or website code
    $this->mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    // Run store or run website
    $this->mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    // Initialize the Mage App and inject the testing request & response
    Mage::app($this->mageRunCode, $this->mageRunType, $this->options);
    Mage::app()->setRequest(new Ibuildings_Mage_Controller_Request_HttpTestCase);
    Mage::app()->setResponse(new Ibuildings_Mage_Controller_Response_HttpTestCase);
    }
    Wednesday, 9 February 2011

    View Slide

  55. /**
    * Bootstrap the Mage application in a similar way to the procedure
    * of index.php
    *
    * Then sets test case request and response objects in Mage_Core_App,
    * and disables returning the response.
    *
    * @return void
    * @author Alistair Stead
    */
    public function mageBootstrap()
    {
    Mage::reset();
    if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
    Mage::setIsDeveloperMode(true);
    }
    // Store or website code
    $this->mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    // Run store or run website
    $this->mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    // Initialize the Mage App and inject the testing request & response
    Mage::app($this->mageRunCode, $this->mageRunType, $this->options);
    Mage::app()->setRequest(new Ibuildings_Mage_Controller_Request_HttpTestCase);
    Mage::app()->setResponse(new Ibuildings_Mage_Controller_Response_HttpTestCase);
    }
    Wednesday, 9 February 2011

    View Slide

  56. Ibuildings_Mage_Controller_Response_HttpTestCase
    Controller
    Ibuildings_Mage_Controller_Request_HttpTestCase
    View
    Model
    Ibuildings_Mage_Controller_Response_HttpTestCase
    Wednesday, 9 February 2011

    View Slide

  57. Ibuildings_Mage_Controller_Response_HttpTestCase
    Controller
    Ibuildings_Mage_Controller_Request_HttpTestCase
    View
    Model
    Ibuildings_Mage_Controller_Request_HttpTestCase
    Ibuildings_Mage_Controller_Response_HttpTestCase
    Wednesday, 9 February 2011

    View Slide

  58. Mage_Core_Model_App
    Ibuildings_Mage_Controller_Response_HttpTestCase
    Controller
    Ibuildings_Mage_Controller_Request_HttpTestCase
    View
    Model
    Ibuildings_Mage_Controller_Request_HttpTestCase
    Ibuildings_Mage_Controller_Response_HttpTestCase
    Wednesday, 9 February 2011

    View Slide

  59. /**
    * Mage_Catalog_IndexControllerTest
    *
    * @package Mage_Catalog
    * @subpackage Mage_Catalog_Test
    *
    *
    * @uses PHPUnit_Framework_Magento_TestCase
    */
    class Mage_Catalog_IndexControllerTest extends Ibuildings_Mage_Test_PHPUnit_ControllerTestCase {
    /**
    * theIndexActionShouldRedirectToRoot
    * @author Alistair Stead
    * @test
    */
    public function theIndexActionShouldRedirectToRoot()
    {
    $this->dispatch('/');
    $this->assertRoute('cms', "The expected cms route has not been matched");
    $this->assertAction('index', "The index action has not been called");
    $this->assertController('index', "The expected controller is not been used");
    $this->assertQuery('div.nav-container', 'The site navigation is not present on the home page');
    } // theIndexActionShouldRedirectToRoot
    }
    Wednesday, 9 February 2011

    View Slide

  60. /**
    * Mage_Catalog_IndexControllerTest
    *
    * @package Mage_Catalog
    * @subpackage Mage_Catalog_Test
    *
    *
    * @uses PHPUnit_Framework_Magento_TestCase
    */
    class Mage_Catalog_IndexControllerTest extends Ibuildings_Mage_Test_PHPUnit_ControllerTestCase {
    /**
    * theIndexActionShouldRedirectToRoot
    * @author Alistair Stead
    * @test
    */
    public function theIndexActionShouldRedirectToRoot()
    {
    $this->dispatch('/');
    $this->assertRoute('cms', "The expected cms route has not been matched");
    $this->assertAction('index', "The index action has not been called");
    $this->assertController('index', "The expected controller is not been used");
    $this->assertQuery('div.nav-container', 'The site navigation is not present on the home page');
    } // theIndexActionShouldRedirectToRoot
    }
    Wednesday, 9 February 2011

    View Slide

  61. /**
    * Mage_Catalog_IndexControllerTest
    *
    * @package Mage_Catalog
    * @subpackage Mage_Catalog_Test
    *
    *
    * @uses PHPUnit_Framework_Magento_TestCase
    */
    class Mage_Catalog_IndexControllerTest extends Ibuildings_Mage_Test_PHPUnit_ControllerTestCase {
    /**
    * theIndexActionShouldRedirectToRoot
    * @author Alistair Stead
    * @test
    */
    public function theIndexActionShouldRedirectToRoot()
    {
    $this->dispatch('/');
    $this->assertRoute('cms', "The expected cms route has not been matched");
    $this->assertAction('index', "The index action has not been called");
    $this->assertController('index', "The expected controller is not been used");
    $this->assertQuery('div.nav-container', 'The site navigation is not present on the home page');
    } // theIndexActionShouldRedirectToRoot
    }
    Wednesday, 9 February 2011

    View Slide

  62. /**
    * theAdminRouteAccessesTheAdminApplicationArea
    * @author Alistair Stead
    * @test
    */
    public function theAdminRouteAccessesTheAdminApplicationArea()
    {
    $this->dispatch('admin/');
    $this->assertRoute('adminhtml', "The expected route has not been matched");
    $this->assertAction('login', "The login form should be presented");
    $this->assertController('index', "The expected controller is not been used");
    } // theAdminRouteAccessesTheAdminApplicationArea
    /**
    * theIndexActionDisplaysLoginForm
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function theIndexActionDisplaysLoginForm()
    {
    $this->dispatch('admin/index/');
    $this->assertQueryCount('form#loginForm', 1);
    } // theIndexActionDisplaysLoginForm
    Wednesday, 9 February 2011

    View Slide

  63. /**
    * theAdminRouteAccessesTheAdminApplicationArea
    * @author Alistair Stead
    * @test
    */
    public function theAdminRouteAccessesTheAdminApplicationArea()
    {
    $this->dispatch('admin/');
    $this->assertRoute('adminhtml', "The expected route has not been matched");
    $this->assertAction('login', "The login form should be presented");
    $this->assertController('index', "The expected controller is not been used");
    } // theAdminRouteAccessesTheAdminApplicationArea
    /**
    * theIndexActionDisplaysLoginForm
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function theIndexActionDisplaysLoginForm()
    {
    $this->dispatch('admin/index/');
    $this->assertQueryCount('form#loginForm', 1);
    } // theIndexActionDisplaysLoginForm
    Wednesday, 9 February 2011

    View Slide

  64. /**
    * theAdminRouteAccessesTheAdminApplicationArea
    * @author Alistair Stead
    * @test
    */
    public function theAdminRouteAccessesTheAdminApplicationArea()
    {
    $this->dispatch('admin/');
    $this->assertRoute('adminhtml', "The expected route has not been matched");
    $this->assertAction('login', "The login form should be presented");
    $this->assertController('index', "The expected controller is not been used");
    } // theAdminRouteAccessesTheAdminApplicationArea
    /**
    * theIndexActionDisplaysLoginForm
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function theIndexActionDisplaysLoginForm()
    {
    $this->dispatch('admin/index/');
    $this->assertQueryCount('form#loginForm', 1);
    } // theIndexActionDisplaysLoginForm
    Wednesday, 9 February 2011

    View Slide

  65. /**
    * theAdminRouteAccessesTheAdminApplicationArea
    * @author Alistair Stead
    * @test
    */
    public function theAdminRouteAccessesTheAdminApplicationArea()
    {
    $this->dispatch('admin/');
    $this->assertRoute('adminhtml', "The expected route has not been matched");
    $this->assertAction('login', "The login form should be presented");
    $this->assertController('index', "The expected controller is not been used");
    } // theAdminRouteAccessesTheAdminApplicationArea
    /**
    * theIndexActionDisplaysLoginForm
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function theIndexActionDisplaysLoginForm()
    {
    $this->dispatch('admin/index/');
    $this->assertQueryCount('form#loginForm', 1);
    } // theIndexActionDisplaysLoginForm
    Wednesday, 9 February 2011

    View Slide

  66. /**
    * theAdminRouteAccessesTheAdminApplicationArea
    * @author Alistair Stead
    * @test
    */
    public function theAdminRouteAccessesTheAdminApplicationArea()
    {
    $this->dispatch('admin/');
    $this->assertRoute('adminhtml', "The expected route has not been matched");
    $this->assertAction('login', "The login form should be presented");
    $this->assertController('index', "The expected controller is not been used");
    } // theAdminRouteAccessesTheAdminApplicationArea
    /**
    * theIndexActionDisplaysLoginForm
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function theIndexActionDisplaysLoginForm()
    {
    $this->dispatch('admin/index/');
    $this->assertQueryCount('form#loginForm', 1);
    } // theIndexActionDisplaysLoginForm
    Wednesday, 9 February 2011

    View Slide

  67. /**
    * submittingInvalidCredsShouldDisplayError
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingInvalidCredsShouldDisplayError()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => 'invalid-password',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertQueryCount('li.error-msg', 1);
    $this->assertQueryContentContains('li.error-msg', 'Invalid Username or Password.');
    } // submittingInvalidCredsShouldDisplayError
    Wednesday, 9 February 2011

    View Slide

  68. /**
    * submittingInvalidCredsShouldDisplayError
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingInvalidCredsShouldDisplayError()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => 'invalid-password',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertQueryCount('li.error-msg', 1);
    $this->assertQueryContentContains('li.error-msg', 'Invalid Username or Password.');
    } // submittingInvalidCredsShouldDisplayError
    Wednesday, 9 February 2011

    View Slide

  69. /**
    * submittingInvalidCredsShouldDisplayError
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingInvalidCredsShouldDisplayError()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => 'invalid-password',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertQueryCount('li.error-msg', 1);
    $this->assertQueryContentContains('li.error-msg', 'Invalid Username or Password.');
    } // submittingInvalidCredsShouldDisplayError
    Wednesday, 9 February 2011

    View Slide

  70. /**
    * submittingInvalidCredsShouldDisplayError
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingInvalidCredsShouldDisplayError()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => 'invalid-password',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertQueryCount('li.error-msg', 1);
    $this->assertQueryContentContains('li.error-msg', 'Invalid Username or Password.');
    } // submittingInvalidCredsShouldDisplayError
    Wednesday, 9 February 2011

    View Slide

  71. /**
    * submittingInvalidCredsShouldDisplayError
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingInvalidCredsShouldDisplayError()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => 'invalid-password',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertQueryCount('li.error-msg', 1);
    $this->assertQueryContentContains('li.error-msg', 'Invalid Username or Password.');
    } // submittingInvalidCredsShouldDisplayError
    Wednesday, 9 February 2011

    View Slide

  72. /**
    * submittingValidCredsShouldDisplayDashboard
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingValidCredsShouldDisplayDashboard()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => '123456',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertRedirect('We should be redirected after login');
    $this->assertRedirectRegex("/^.*dashboard.*$/", 'We are not directed to the dashboard');
    } // submittingValidCredsShouldDisplayDashboard
    Wednesday, 9 February 2011

    View Slide

  73. /**
    * submittingValidCredsShouldDisplayDashboard
    * @author Alistair Stead
    * @group login
    * @test
    */
    public function submittingValidCredsShouldDisplayDashboard()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => '123456',
    )
    )
    );
    $this->dispatch('admin/index/login');
    $this->assertRedirect('We should be redirected after login');
    $this->assertRedirectRegex("/^.*dashboard.*$/", 'We are not directed to the dashboard');
    } // submittingValidCredsShouldDisplayDashboard
    Wednesday, 9 February 2011

    View Slide

  74. class Mage_Adminhtml_SalesControllerTest extends Mage_Adminhtml_ControllerTestCase {
    /**
    * indexActionListsOrders
    * @author Alistair Stead
    * @test
    */
    public function indexActionListsOrders()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => '123456',
    )
    )
    );
    $this->dispatch('admin/index/login');
    // Reset the requests after login before next dispatch
    $this->reset();
    $this->dispatch('admin/sales_order/index');
    $this->assertQueryContentContains('h3.icon-head', 'Orders');
    } // indexActionListsOrders
    }
    Wednesday, 9 February 2011

    View Slide

  75. class Mage_Adminhtml_SalesControllerTest extends Mage_Adminhtml_ControllerTestCase {
    /**
    * indexActionListsOrders
    * @author Alistair Stead
    * @test
    */
    public function indexActionListsOrders()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => '123456',
    )
    )
    );
    $this->dispatch('admin/index/login');
    // Reset the requests after login before next dispatch
    $this->reset();
    $this->dispatch('admin/sales_order/index');
    $this->assertQueryContentContains('h3.icon-head', 'Orders');
    } // indexActionListsOrders
    }
    Wednesday, 9 February 2011

    View Slide

  76. class Mage_Adminhtml_SalesControllerTest extends Mage_Adminhtml_ControllerTestCase {
    /**
    * indexActionListsOrders
    * @author Alistair Stead
    * @test
    */
    public function indexActionListsOrders()
    {
    $this->request->setMethod('POST')
    ->setPost(
    array(
    'login' => array(
    'username' => 'admin',
    'password' => '123456',
    )
    )
    );
    $this->dispatch('admin/index/login');
    // Reset the requests after login before next dispatch
    $this->reset();
    $this->dispatch('admin/sales_order/index');
    $this->assertQueryContentContains('h3.icon-head', 'Orders');
    } // indexActionListsOrders
    }
    Wednesday, 9 February 2011

    View Slide

  77. TESTING EMAIL CONTENT
    Who gets all the emails sent to [email protected]?
    Wednesday, 9 February 2011

    View Slide

  78. public function mageBootstrap()
    {
    Mage::reset();
    if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
    Mage::setIsDeveloperMode(true);
    }
    // Store or website code
    $this->mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    // Run store or run website
    $this->mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    // Initialize the Mage App and inject the testing request & response
    Mage::app($this->mageRunCode, $this->mageRunType, $this->options);
    Mage::app()->setRequest(new Ibuildings_Mage_Controller_Request_HttpTestCase);
    Mage::app()->setResponse(new Ibuildings_Mage_Controller_Response_HttpTestCase);
    // Rewrite the core classes at runtime to prevent emails from being sent
    Mage::getConfig()->setNode('global/models/core/rewrite/email_template',
    'Ibuildings_Test_Model_Email_Template');
    // This is a hack to get the runtime config changes to take effect
    Mage::getModel('core/email_template');
    }
    Wednesday, 9 February 2011

    View Slide

  79. public function mageBootstrap()
    {
    Mage::reset();
    if (isset($_SERVER['MAGE_IS_DEVELOPER_MODE'])) {
    Mage::setIsDeveloperMode(true);
    }
    // Store or website code
    $this->mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    // Run store or run website
    $this->mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    // Initialize the Mage App and inject the testing request & response
    Mage::app($this->mageRunCode, $this->mageRunType, $this->options);
    Mage::app()->setRequest(new Ibuildings_Mage_Controller_Request_HttpTestCase);
    Mage::app()->setResponse(new Ibuildings_Mage_Controller_Response_HttpTestCase);
    // Rewrite the core classes at runtime to prevent emails from being sent
    Mage::getConfig()->setNode('global/models/core/rewrite/email_template',
    'Ibuildings_Test_Model_Email_Template');
    // This is a hack to get the runtime config changes to take effect
    Mage::getModel('core/email_template');
    }
    Wednesday, 9 February 2011

    View Slide

  80. /**
    * submittingForgotPasswordWithValidEmailReturnsSuccess
    * @author Alistair Stead
    * @group password
    * @test
    *
    */
    public function submittingForgotPasswordWithValidEmailReturnsSuccess()
    {
    $this->request->setMethod('POST')
    ->setPost(array('email' => $this->email));
    $this->dispatch('admin/index/forgotpassword/');
    $this->assertQueryCount('li.success-msg', 1);
    $this->assertQueryContentContains('li.success-msg', 'A new password was sent to your email
    address. Please check your email and click Back to Login.');
    // Test that the email contains the correct data
    $emailContent = $this->getResponseEmail()
    ->getBodyHtml()
    ->getContent();
    // Overriding the response body to be able to use the standard content assertions
    $this->response->setBody($emailContent);
    // The email content addresses the fixture user
    $this->assertQueryContentContains('body', "Dear $this->firstName $this->lastName");
    // The fixture users password has been changed
    $this->assertNotQueryContentContains('body', $this->password);
    } // submittingForgotPasswordWithValidEmailReturnsSuccess
    Wednesday, 9 February 2011

    View Slide

  81. /**
    * submittingForgotPasswordWithValidEmailReturnsSuccess
    * @author Alistair Stead
    * @group password
    * @test
    *
    */
    public function submittingForgotPasswordWithValidEmailReturnsSuccess()
    {
    $this->request->setMethod('POST')
    ->setPost(array('email' => $this->email));
    $this->dispatch('admin/index/forgotpassword/');
    $this->assertQueryCount('li.success-msg', 1);
    $this->assertQueryContentContains('li.success-msg', 'A new password was sent to your email
    address. Please check your email and click Back to Login.');
    // Test that the email contains the correct data
    $emailContent = $this->getResponseEmail()
    ->getBodyHtml()
    ->getContent();
    // Overriding the response body to be able to use the standard content assertions
    $this->response->setBody($emailContent);
    // The email content addresses the fixture user
    $this->assertQueryContentContains('body', "Dear $this->firstName $this->lastName");
    // The fixture users password has been changed
    $this->assertNotQueryContentContains('body', $this->password);
    } // submittingForgotPasswordWithValidEmailReturnsSuccess
    Wednesday, 9 February 2011

    View Slide

  82. /**
    * submittingForgotPasswordWithValidEmailReturnsSuccess
    * @author Alistair Stead
    * @group password
    * @test
    *
    */
    public function submittingForgotPasswordWithValidEmailReturnsSuccess()
    {
    $this->request->setMethod('POST')
    ->setPost(array('email' => $this->email));
    $this->dispatch('admin/index/forgotpassword/');
    $this->assertQueryCount('li.success-msg', 1);
    $this->assertQueryContentContains('li.success-msg', 'A new password was sent to your email
    address. Please check your email and click Back to Login.');
    // Test that the email contains the correct data
    $emailContent = $this->getResponseEmail()
    ->getBodyHtml()
    ->getContent();
    // Overriding the response body to be able to use the standard content assertions
    $this->response->setBody($emailContent);
    // The email content addresses the fixture user
    $this->assertQueryContentContains('body', "Dear $this->firstName $this->lastName");
    // The fixture users password has been changed
    $this->assertNotQueryContentContains('body', $this->password);
    } // submittingForgotPasswordWithValidEmailReturnsSuccess
    Wednesday, 9 February 2011

    View Slide

  83. /**
    * submittingForgotPasswordWithValidEmailReturnsSuccess
    * @author Alistair Stead
    * @group password
    * @test
    *
    */
    public function submittingForgotPasswordWithValidEmailReturnsSuccess()
    {
    $this->request->setMethod('POST')
    ->setPost(array('email' => $this->email));
    $this->dispatch('admin/index/forgotpassword/');
    $this->assertQueryCount('li.success-msg', 1);
    $this->assertQueryContentContains('li.success-msg', 'A new password was sent to your email
    address. Please check your email and click Back to Login.');
    // Test that the email contains the correct data
    $emailContent = $this->getResponseEmail()
    ->getBodyHtml()
    ->getContent();
    // Overriding the response body to be able to use the standard content assertions
    $this->response->setBody($emailContent);
    // The email content addresses the fixture user
    $this->assertQueryContentContains('body', "Dear $this->firstName $this->lastName");
    // The fixture users password has been changed
    $this->assertNotQueryContentContains('body', $this->password);
    } // submittingForgotPasswordWithValidEmailReturnsSuccess
    Wednesday, 9 February 2011

    View Slide

  84. SO WHAT DO AUTOMATED
    TESTS GIVE US?
    Wednesday, 9 February 2011

    View Slide

  85. CODE COVERAGE
    Wednesday, 9 February 2011

    View Slide

  86. STATIC CODE ANALYSIS
    Wednesday, 9 February 2011

    View Slide

  87. CONTINUOUS INTEGRATION
    Wednesday, 9 February 2011

    View Slide

  88. Wednesday, 9 February 2011

    View Slide

  89. Wednesday, 9 February 2011

    View Slide

  90. SO WHAT NOW?
    Wednesday, 9 February 2011

    View Slide

  91. MAGE-TEST IS OPEN SOURCE
    • Use Mage-Test to test your development projects
    • Use Mage-Test to test you extensions
    • Contribute tests for Magento
    • Build up the coverage of the Magento codebase
    • Take advantage of core tests for regression testing
    Wednesday, 9 February 2011

    View Slide

  92. MAGE-TEST
    http://github.com/ibuildings/Mage-Test
    Wednesday, 9 February 2011

    View Slide

  93. THANK YOU!
    • Email: [email protected]
    • Skype: astead-ibuildings
    • Twitter: @alistairstead
    Wednesday, 9 February 2011

    View Slide

  94. REFERENCES
    • PHPUnit https://github.com/sebastianbergmann/phpunit/
    • Zend_Test http://framework.zend.com/manual/en/
    zend.test.html
    • Mage-Test https://github.com/ibuildings/Mage-Test
    • phpUnderControl http://phpundercontrol.org/
    • Bamboo http://www.atlassian.com/software/bamboo/
    Wednesday, 9 February 2011

    View Slide

  95. IMAGE CREDITS
    BA Plane: http://www.flickr.com/photos/bribri/1299325208/sizes/l/in/photostream/
    Minions: http://www.akblessingsabound.com/wp-content/uploads/2010/06/despicable-me-minions-
    blessings-abound-mommy.jpg
    Eleventh Hour: http://www.flickr.com/photos/d4dee/2258343575/sizes/l/in/photostream/
    Deep Thought: http://www.flickr.com/photos/8640416@N02/4213361072/sizes/l/in/photostream/
    US Dollar: http://www.flickr.com/photos/8640416@N02/4213361072/sizes/l/in/photostream/
    Whoa (Stop Sign): http://www.flickr.com/photos/aquaoracle/3265987824/sizes/l/
    Departures Board: http://www.flickr.com/photos/scottmulhollan/4892422469/sizes/l/in/photostream/
    Syringe: http://www.flickr.com/photos/woodypics/3809842998/sizes/z/in/photostream/
    Coupled Carriages: http://www.flickr.com/photos/jowo/89657494/sizes/o/in/photostream/
    Wednesday, 9 February 2011

    View Slide

  96. QUESTIONS?
    Wednesday, 9 February 2011

    View Slide

  97. WE ARE HIRING!
    http://www.ibuildings.co.uk/about/careers/
    Wednesday, 9 February 2011

    View Slide