Writing testable Code (MageTitans Mini 2016)

Preview:

Citation preview

WRITINGTESTABLE

CODEWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Vinai KoppFreelance Developer & Trainer

Tweet me @VinaiKopp

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

I want to get into testing

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

" WANT TO ""I have to GET INTO TESTING"

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

CHOOSE A TOOL +LEARN THE SYNTAX

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ PHPUnit▸ PHPSpec▸ Codeception▸ nvm...

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

That wasn't so bad!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Current status:

A metric ton of untested legacy code

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Start with writing tests for bugs

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

THIS IS HARDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

This costs lots of time!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

This is painful!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

There has to bea better way...!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

<?php

/** * @todo make more testable *

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Things to keep in mindto write TESTABLE CODE

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

KEEP CLASSES

SMALLWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

A bad example:

Event Observer

(for Magento 1)

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

<?php

use Varien_Event_Observer as Event;

class Netzarbeiter_CustomerActivation_Model_Observer{ // Check if the customer has been activated, if not, throw login error public function customerLogin(Event $event) {...}

// Flag new accounts as such public function customerSaveBefore(Event $event) {...}

// Send out emails public function customerSaveAfter(Event $event) {...}

// Abort registration during checkout if default activation status is false public function salesConvertQuoteAddressToOrder(Event $event) {...}

// Add customer activation option to the mass action block public function adminhtmlBlockHtmlBefore(Event $event) {...}

// Add the customer_activated attribute to the customer grid collection public function eavCollectionAbstractLoadBefore(Event $event) {...}

// Add customer_activated column to CSV and XML exports public function coreBlockAbstractPrepareLayoutAfter(Event $event) {...}

// Remove the customer id from the customer/session, in effect causing a logout just in case public function controllerActionPostdispatchCustomerAccountResetPasswordPost(Event $event) {...}}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

SMALLER?HOW?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

First attempt:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

WHAT DOES IT DO?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

1. Prevent inactive customer logins2. Send notification emails

3. Add column to customer grid

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

splitNetzarbeiter_CustomerActivation_Model_Observer

into..._Model_Observer_ProhibitInactiveLogins..._Model_Observer_EmailNotifications..._Model_Observer_AdminhtmlCustomerGrid

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

<?php

use Varien_Event_Observer as Event;

class Netzarbeiter_CustomerActivation_Model_Observer_ProhibitInactiveLogin{ // Check if the customer has been activated, if not, throw login error public function customerLogin(Event $event) {...}

// Abort registration during checkout if default activation status is false public function salesConvertQuoteAddressToOrder(Event $event) {...}

// Remove the customer id from the customer/session, in effect causing a logout just in case public function controllerActionPostdispatchCustomerAccountResetPasswordPost(Event $event) {...}}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

<?php

use Varien_Event_Observer as Event;

class Netzarbeiter_CustomerActivation_Model_Observer_EmailNotifications{ // Flag new accounts as such public function customerSaveBefore(Event $event) {...}

// Send out emails public function customerSaveAfter(Event $event) {...}}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

<?php

use Varien_Event_Observer as Event;

class Netzarbeiter_CustomerActivation_Model_Observer_AdminhtmlCustomerGrid{ // Add customer activation option to the mass action block public function adminhtmlBlockHtmlBefore(Event $event) {...}

// Add the customer_activated attribute to the customer grid collection public function eavCollectionAbstractLoadBefore(Event $event) {...}

// Add customer_activated column to CSV and XML exports public function coreBlockAbstractPrepareLayoutAfter(Event $event) {...}}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Pretty rough...

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Okay first step

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

MINOR DIFFERENCE INTESTING EFFORT

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Second attempt:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Look closer

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Dependencies:Netzarbeiter_CustomerActivation_Helper_DataMage_Customer_Model_CustomerMage_Customer_Model_SessionMage_Customer_Model_GroupMage_Customer_Helper_AddressMage_Customer_Model_Resource_Customer_CollectionMage_Core_Controller_Request_HttpMage_Core_Controller_Response_HttpMage_Core_ExceptionMage_Core_Model_SessionMage_Core_Model_StoreMage_Sales_Model_Quote_AddressMage_Sales_Model_QuoteMage_Eav_Model_ConfigMage_Eav_Model_Entity_TypeMage_Adminhtml_Block_Widget_Grid_MassactionMage_Adminhtml_Block_Widget_Grid

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Business logic is hidden in Observer or HelperNetzarbeiter_CustomerActivation_Model_ObserverNetzarbeiter_CustomerActivation_Helper_Data

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Observers link

Business logic!

Magento

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Old Code:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

public function customerLogin($observer){ $helper = Mage::helper('customeractivation'); if (!$helper->isModuleActive()) { return; }

if ($this->_isApiRequest()) { return; }

$customer = $observer->getEvent()->getCustomer(); $session = Mage::getSingleton('customer/session');

if (!$customer->getCustomerActivated()) { $session->setCustomer(Mage::getModel('customer/customer')) ->setId(null) ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID);

if ($this->_checkRequestRoute('customer', 'account', 'createpost')) { $message = $helper->__('Please wait for your account to be activated');

$session->addSuccess($message); } else { Mage::throwException($helper->__('This account is not activated.')); } }}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

New Code:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

public function customerLogin(Event $event){ if (! $this->isModuleActive()) { return; }

$this->getCustomerLoginSentry()->abortLoginIfNotActive( $event->getData('customer') );}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Details are hidden▸ Delegation

▸ No magic getters

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

private static $sentry = 'customeractivation/customerLoginSentry';

/** * @return Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry */private function getCustomerLoginSentry(){ return isset($this->loginSentry) ? $this->loginSentry : Mage::getModel(self::$sentry);}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Dependencies can be injected▸ Business logic moved into model

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Model with specific responsibilityclass Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

public function abortLoginIfNotActive(Mage_Customer_Model_Customer $customer){ if (! $customer->getData('customer_activated') { $this->getSession()->logout(); $this->getDisplay()->showLoginAbortedMessage(); }}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Business logic independent of entry point▸ More type safety

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

private function getSession(){ return isset($this->session) ? $this->session : Mage::getModel('customeractivation/session');}

private function getDisplay(){ return isset($this->display) ? $this->display : Mage::getModel('customeractivation/display');}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Injectable dependencies▸ Business logic in specific models

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Need something done?

Need some information?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

"I don't care how it's done!"

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Delegate!Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

IDCDDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

I Don't Care Driven Development

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

I Don't Care Driven Design

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

The result?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

CLASS EXPLOSION!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

SMALL CLASSES

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

TrivialTO TEST

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

How far can delegation go?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Delegate until the nextdelegator == delegatee

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

In the endour class

is wrappinga Magento class

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

In the endour class

is wrappinga Framework class

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

DECOUPLING FROMTHE FRAMEWORK

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

This is a Good Thing

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

ONE

FAQWHEN STARTING WITH TESTING

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

WHAT TO TEST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Better question:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

WHY DOESTHE CLASS EXIST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

WHY DOESTHE METHOD EXIST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

RETURN A VALUE

ORSIDE EFFECT

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

If a method

RETURNS A VALUEonly test for that

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

If a method

CAUSES A SIDE EFFECTonly test for that

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

SIDE EFFECTS?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Side Effect #1:Global State

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Filesystem▸ Databases

▸ Global Functions + Variables▸ Static Method Calls + Properties

▸ Forking

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Side Effect #2:A method call on another object

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

if (! $customer->getData('customer_activated') { $this->getSession()->logout(); $this->getDisplay()->showLoginAbortedMessage();}

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

TESTINGRETURN VALUES

IS EASIER THANSIDE EFFECTS

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Avoid creating methods that do both:side effect && return value

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

ALSO TEST FOREXCEPTIONS

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Key PointsWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Separation betweenHooks or Entry Points

andBusiness Logic

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Split Business Logic into specific classes

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Use IDCDD to find where to separate

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Also separate code thatreturns a valuefrom code that

causes a side effect

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Test if a class fulfills it's purpose

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Don't use magic methods

(No more calls to __call())

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Avoid method call chaining

(Don't return $this);

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

▸ Mainly for Magento 1.x:Make dependencies injectable for testing

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

...and have fun testing!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

(tell you me comment)(ask? you me question)

(thank you)

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - contact@vinaikopp.com - twitter://@VinaiKopp

Recommended