writing testable code (magetitans mini 2016)

89
WRITING TESTABLE CODE Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Upload: vinaikopp

Post on 08-Jan-2017

925 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Writing testable Code (MageTitans Mini 2016)

WRITINGTESTABLE

CODEWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 2: Writing testable Code (MageTitans Mini 2016)

Vinai KoppFreelance Developer & Trainer

Tweet me @VinaiKopp

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 3: Writing testable Code (MageTitans Mini 2016)

I want to get into testing

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 4: Writing testable Code (MageTitans Mini 2016)

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 5: Writing testable Code (MageTitans Mini 2016)

CHOOSE A TOOL +LEARN THE SYNTAX

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 6: Writing testable Code (MageTitans Mini 2016)

▸ PHPUnit▸ PHPSpec▸ Codeception▸ nvm...

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 7: Writing testable Code (MageTitans Mini 2016)

That wasn't so bad!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 8: Writing testable Code (MageTitans Mini 2016)

Current status:

A metric ton of untested legacy code

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 9: Writing testable Code (MageTitans Mini 2016)

Start with writing tests for bugs

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 10: Writing testable Code (MageTitans Mini 2016)

THIS IS HARDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 11: Writing testable Code (MageTitans Mini 2016)

This costs lots of time!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 12: Writing testable Code (MageTitans Mini 2016)

This is painful!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 13: Writing testable Code (MageTitans Mini 2016)

There has to bea better way...!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 14: Writing testable Code (MageTitans Mini 2016)

<?php

/** * @todo make more testable *

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 15: Writing testable Code (MageTitans Mini 2016)

Things to keep in mindto write TESTABLE CODE

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 16: Writing testable Code (MageTitans Mini 2016)

KEEP CLASSES

SMALLWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 17: Writing testable Code (MageTitans Mini 2016)

A bad example:

Event Observer

(for Magento 1)

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 18: Writing testable Code (MageTitans Mini 2016)

<?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 - ! - [email protected] - twitter://@VinaiKopp

Page 19: Writing testable Code (MageTitans Mini 2016)

SMALLER?HOW?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 20: Writing testable Code (MageTitans Mini 2016)

First attempt:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 21: Writing testable Code (MageTitans Mini 2016)

WHAT DOES IT DO?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 22: Writing testable Code (MageTitans Mini 2016)

1. Prevent inactive customer logins2. Send notification emails

3. Add column to customer grid

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 23: Writing testable Code (MageTitans Mini 2016)

splitNetzarbeiter_CustomerActivation_Model_Observer

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 24: Writing testable Code (MageTitans Mini 2016)

<?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 - ! - [email protected] - twitter://@VinaiKopp

Page 25: Writing testable Code (MageTitans Mini 2016)

<?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 - ! - [email protected] - twitter://@VinaiKopp

Page 26: Writing testable Code (MageTitans Mini 2016)

<?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 - ! - [email protected] - twitter://@VinaiKopp

Page 27: Writing testable Code (MageTitans Mini 2016)

Pretty rough...

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 28: Writing testable Code (MageTitans Mini 2016)

Okay first step

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 29: Writing testable Code (MageTitans Mini 2016)

MINOR DIFFERENCE INTESTING EFFORT

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 30: Writing testable Code (MageTitans Mini 2016)

Second attempt:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 31: Writing testable Code (MageTitans Mini 2016)

Look closer

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 32: Writing testable Code (MageTitans Mini 2016)

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 - ! - [email protected] - twitter://@VinaiKopp

Page 33: Writing testable Code (MageTitans Mini 2016)

Business logic is hidden in Observer or HelperNetzarbeiter_CustomerActivation_Model_ObserverNetzarbeiter_CustomerActivation_Helper_Data

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 34: Writing testable Code (MageTitans Mini 2016)

Observers link

Business logic!

Magento

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 35: Writing testable Code (MageTitans Mini 2016)

Old Code:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 36: Writing testable Code (MageTitans Mini 2016)

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 - ! - [email protected] - twitter://@VinaiKopp

Page 37: Writing testable Code (MageTitans Mini 2016)

New Code:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 38: Writing testable Code (MageTitans Mini 2016)

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

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 39: Writing testable Code (MageTitans Mini 2016)

▸ Details are hidden▸ Delegation

▸ No magic getters

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 40: Writing testable Code (MageTitans Mini 2016)

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 - ! - [email protected] - twitter://@VinaiKopp

Page 41: Writing testable Code (MageTitans Mini 2016)

▸ Dependencies can be injected▸ Business logic moved into model

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 42: Writing testable Code (MageTitans Mini 2016)

Model with specific responsibilityclass Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 43: Writing testable Code (MageTitans Mini 2016)

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 - ! - [email protected] - twitter://@VinaiKopp

Page 44: Writing testable Code (MageTitans Mini 2016)

▸ Business logic independent of entry point▸ More type safety

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 45: Writing testable Code (MageTitans Mini 2016)

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 - ! - [email protected] - twitter://@VinaiKopp

Page 46: Writing testable Code (MageTitans Mini 2016)

▸ Injectable dependencies▸ Business logic in specific models

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 47: Writing testable Code (MageTitans Mini 2016)

Need something done?

Need some information?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 48: Writing testable Code (MageTitans Mini 2016)

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 49: Writing testable Code (MageTitans Mini 2016)

Delegate!Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 50: Writing testable Code (MageTitans Mini 2016)

IDCDDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 51: Writing testable Code (MageTitans Mini 2016)

I Don't Care Driven Development

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 52: Writing testable Code (MageTitans Mini 2016)

I Don't Care Driven Design

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 53: Writing testable Code (MageTitans Mini 2016)

The result?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 54: Writing testable Code (MageTitans Mini 2016)

CLASS EXPLOSION!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 55: Writing testable Code (MageTitans Mini 2016)

SMALL CLASSES

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 56: Writing testable Code (MageTitans Mini 2016)

TrivialTO TEST

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 57: Writing testable Code (MageTitans Mini 2016)

How far can delegation go?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 58: Writing testable Code (MageTitans Mini 2016)

Delegate until the nextdelegator == delegatee

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 59: Writing testable Code (MageTitans Mini 2016)

In the endour class

is wrappinga Magento class

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 60: Writing testable Code (MageTitans Mini 2016)

In the endour class

is wrappinga Framework class

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 61: Writing testable Code (MageTitans Mini 2016)

DECOUPLING FROMTHE FRAMEWORK

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 62: Writing testable Code (MageTitans Mini 2016)

This is a Good Thing

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 63: Writing testable Code (MageTitans Mini 2016)

ONE

FAQWHEN STARTING WITH TESTING

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 64: Writing testable Code (MageTitans Mini 2016)

WHAT TO TEST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 65: Writing testable Code (MageTitans Mini 2016)

Better question:

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 66: Writing testable Code (MageTitans Mini 2016)

WHY DOESTHE CLASS EXIST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 67: Writing testable Code (MageTitans Mini 2016)

WHY DOESTHE METHOD EXIST?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 68: Writing testable Code (MageTitans Mini 2016)

RETURN A VALUE

ORSIDE EFFECT

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 69: Writing testable Code (MageTitans Mini 2016)

If a method

RETURNS A VALUEonly test for that

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 70: Writing testable Code (MageTitans Mini 2016)

If a method

CAUSES A SIDE EFFECTonly test for that

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 71: Writing testable Code (MageTitans Mini 2016)

SIDE EFFECTS?

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 72: Writing testable Code (MageTitans Mini 2016)

Side Effect #1:Global State

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 73: Writing testable Code (MageTitans Mini 2016)

▸ Filesystem▸ Databases

▸ Global Functions + Variables▸ Static Method Calls + Properties

▸ Forking

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 74: Writing testable Code (MageTitans Mini 2016)

Side Effect #2:A method call on another object

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 75: Writing testable Code (MageTitans Mini 2016)

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 76: Writing testable Code (MageTitans Mini 2016)

TESTINGRETURN VALUES

IS EASIER THANSIDE EFFECTS

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 77: Writing testable Code (MageTitans Mini 2016)

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 78: Writing testable Code (MageTitans Mini 2016)

ALSO TEST FOREXCEPTIONS

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 79: Writing testable Code (MageTitans Mini 2016)

Key PointsWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 80: Writing testable Code (MageTitans Mini 2016)

▸ Separation betweenHooks or Entry Points

andBusiness Logic

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 81: Writing testable Code (MageTitans Mini 2016)

▸ Split Business Logic into specific classes

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 82: Writing testable Code (MageTitans Mini 2016)

▸ Use IDCDD to find where to separate

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 83: Writing testable Code (MageTitans Mini 2016)

▸ Also separate code thatreturns a valuefrom code that

causes a side effect

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 84: Writing testable Code (MageTitans Mini 2016)

▸ Test if a class fulfills it's purpose

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 85: Writing testable Code (MageTitans Mini 2016)

▸ Don't use magic methods

(No more calls to __call())

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 86: Writing testable Code (MageTitans Mini 2016)

▸ Avoid method call chaining

(Don't return $this);

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 87: Writing testable Code (MageTitans Mini 2016)

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

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 88: Writing testable Code (MageTitans Mini 2016)

...and have fun testing!

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp

Page 89: Writing testable Code (MageTitans Mini 2016)

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

(thank you)

Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp