Upload
vinaikopp
View
928
Download
0
Embed Size (px)
Citation preview
WRITINGTESTABLE
CODEWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Vinai KoppFreelance Developer & Trainer
Tweet me @VinaiKopp
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
I want to get into testing
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
" WANT TO ""I have to GET INTO TESTING"
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
CHOOSE A TOOL +LEARN THE SYNTAX
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ PHPUnit▸ PHPSpec▸ Codeception▸ nvm...
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
That wasn't so bad!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Current status:
A metric ton of untested legacy code
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Start with writing tests for bugs
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
THIS IS HARDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
This costs lots of time!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
This is painful!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
There has to bea better way...!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
<?php
/** * @todo make more testable *
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Things to keep in mindto write TESTABLE CODE
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
KEEP CLASSES
SMALLWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
A bad example:
Event Observer
(for Magento 1)
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
SMALLER?HOW?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
First attempt:
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
WHAT DOES IT DO?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
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
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
<?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
<?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
<?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
Pretty rough...
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Okay first step
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
MINOR DIFFERENCE INTESTING EFFORT
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Second attempt:
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Look closer
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
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
Observers link
Business logic!
Magento
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Old Code:
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
New Code:
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
▸ Details are hidden▸ Delegation
▸ No magic getters
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
▸ Dependencies can be injected▸ Business logic moved into model
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Model with specific responsibilityclass Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
▸ Business logic independent of entry point▸ More type safety
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - 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 - ! - [email protected] - twitter://@VinaiKopp
▸ Injectable dependencies▸ Business logic in specific models
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Need something done?
Need some information?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
"I don't care how it's done!"
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Delegate!Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
IDCDDWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
I Don't Care Driven Development
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
I Don't Care Driven Design
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
The result?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
CLASS EXPLOSION!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
SMALL CLASSES
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
TrivialTO TEST
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
How far can delegation go?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Delegate until the nextdelegator == delegatee
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
In the endour class
is wrappinga Magento class
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
In the endour class
is wrappinga Framework class
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
DECOUPLING FROMTHE FRAMEWORK
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
This is a Good Thing
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
ONE
FAQWHEN STARTING WITH TESTING
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
WHAT TO TEST?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Better question:
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
WHY DOESTHE CLASS EXIST?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
WHY DOESTHE METHOD EXIST?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
RETURN A VALUE
ORSIDE EFFECT
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
If a method
RETURNS A VALUEonly test for that
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
If a method
CAUSES A SIDE EFFECTonly test for that
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
SIDE EFFECTS?
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Side Effect #1:Global State
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Filesystem▸ Databases
▸ Global Functions + Variables▸ Static Method Calls + Properties
▸ Forking
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Side Effect #2:A method call on another object
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
if (! $customer->getData('customer_activated') { $this->getSession()->logout(); $this->getDisplay()->showLoginAbortedMessage();}
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
TESTINGRETURN VALUES
IS EASIER THANSIDE EFFECTS
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Avoid creating methods that do both:side effect && return value
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
ALSO TEST FOREXCEPTIONS
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
Key PointsWriting Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Separation betweenHooks or Entry Points
andBusiness Logic
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Split Business Logic into specific classes
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Use IDCDD to find where to separate
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Also separate code thatreturns a valuefrom code that
causes a side effect
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Test if a class fulfills it's purpose
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Don't use magic methods
(No more calls to __call())
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Avoid method call chaining
(Don't return $this);
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
▸ Mainly for Magento 1.x:Make dependencies injectable for testing
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
...and have fun testing!
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp
(tell you me comment)(ask? you me question)
(thank you)
Writing Testable Code - MageTitans Mini, 5th May 2016 - ! - [email protected] - twitter://@VinaiKopp