Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Writing Testable Code
(for Magento 1 and 2)
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vin...
Assumptions!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
You know basic PHPUnit.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
You want
→ Confidence in deploys
→ Experience joy when writing tests
→ Have fun doing code maintaince
→ Get more $$$ out of...
In short, you want
→ Testable code
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
When is code "testable"?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
When testing
is simple & easy.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
What makes a
test simple?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
It is simple to write.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
It is easy to read.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
What does
"easy to read"
mean?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
It's intent is clear.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
The test is short.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Good names.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
It only does
one thing.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Clean Code
is for
Production Code
& Test Code
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@V...
What does
"simple to write"
mean?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Test code
depends on
production code
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
It are properties of the
production code
that make testing
easy or hard.
Writing Testable Code in Magento 1 and 2 - #MM16R...
What does easy to test
code look like?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Magento 1 Example:
Event Observer
(Legacy Code)
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://...
<?php
use Varien_Event_Observer as Event;
class Netzarbeiter_CustomerActivation_Model_Observer
{
// Check if the customer ...
Are we going to write
Unit Tests?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Hell NO!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Unit tests only provide
value for new code.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vin...
For previously untested code,
Integration Tests
are much more valuable.
Writing Testable Code in Magento 1 and 2 - #MM16RO...
What would make it
simpler to test?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
If the class where
smallerit would be simpler to test.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twi...
First attempt:
Splitting the class based on purpose.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitt...
What does the
class do?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
1. Prevents inactive customer logins.
2. Sends notification emails.
3. Adds a column to the customer grid.
Writing Testable...
Lets split it into
Netzarbeiter_CustomerActivation_Model...
..._Observer_ProhibitInactiveLogins
..._Observer_EmailNotifica...
<?php
use Varien_Event_Observer as Event;
class Netzarbeiter_CustomerActivation_Model_Observer_ProhibitInactiveLogin
{
// ...
<?php
use Varien_Event_Observer as Event;
class Netzarbeiter_CustomerActivation_Model_Observer_EmailNotifications
{
// Fla...
<?php
use Varien_Event_Observer as Event;
class Netzarbeiter_CustomerActivation_Model_Observer_AdminhtmlCustomerGrid
{
// ...
Is this simpler
to test?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Only minor difference in
testing effort.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiK...
Why?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
The same tests as before.
Only split into three classes.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - t...
Second attempt:
Lets go beyond
superficial changes.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter...
Lets look at the design.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
What collaborators are used?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Collaborators:
Netzarbeiter_CustomerActivation_Helper_Data
Mage_Customer_Model_Customer
Mage_Customer_Model_Session
Mage_C...
Almost all of them are core classes.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Only two classes are part of the module:
Netzarbeiter_CustomerActivation_Model_Observer
Netzarbeiter_CustomerActivation_He...
Based on the names,
why do these classes exist?
Netzarbeiter_CustomerActivation_Model_Observer
Netzarbeiter_CustomerActiva...
The names don't
tell us anything.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Extract parts by giving them
meaningful names
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@V...
But where to start?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Separate business logic
from entry points.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vina...
Entry points are the places Magento provides for
our custom code.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-...
Entry points:
→ Observers
→ Plugins
→ Controllers
→ Cron Jobs
→ Preferences
→ Console Commands
Writing Testable Code in Ma...
Entry points link
Business logic
!
Magento
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vina...
Remove all
Business Logic
from
Entry Points.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vi...
What are the benefits?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
For testing:
The custom code can be triggered
independently of the entry point.
Writing Testable Code in Magento 1 and 2 -...
In our example,
what is the
entry point?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiK...
Observer
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Old Observer Code:
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
public function customerLogin($observer)
{
$helper = Mage::helper('customeractivation');
if (!$helper->isModuleActive()) {...
New Code, without business logic:
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
public function customerLogin(Event $event)
{
if (! $this->isModuleActive()) {
return;
}
$this->getCustomerLoginSentry()->...
And this class is
simpler to test?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Yes, as there is
much less logic.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Most of the logic is delegated to collaborators.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter:/...
$this->getCustomerLoginSentry()->abortLoginIfNotActive(
$event->getData('customer')
);
Writing Testable Code in Magento 1 ...
How does the delegation
look in detail?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKo...
private static $sentryClass = 'customeractivation/customerLoginSentry';
/**
* @return CustomerLoginSentry
*/
private funct...
The login sentry can be injected.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
That means,
it can be replaced by a test double.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter:/...
Dependency Injection
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
DI is a Magento 2 thing,
right?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
DI can be everywhere!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Injecting Test Doubles
in Magento 1
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Setter Injection
public function testDelegatesToLoginSentry()
{
$mockLoginSentry = $this->createMock(LoginSentry::class);
...
Constructor Injection
public function testDelegatesToLoginSentry()
{
$mockLoginSentry = $this->createMock(LoginSentry::cla...
Ugly but hey it works.
/**
* @param LoginSentry $loginSentry
*/
public function __construct($loginSentry = null)
{
$this->...
Injected collaborators
make for simple tests!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@V...
Delegation allow us to create
classes with a specific purpose.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-2...
We can give descriptive names.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Model with one responsibility:
class Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry
Writing Testable Code in Ma...
public function abortLoginIfNotActive(
Mage_Customer_Model_Customer $customer
) {
if (! $customer->getData('customer_activ...
This business logic is now
independent of the entry point.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 -...
It can be called from anywhere:
→ Test
→ Observer
→ Controller
→ Model Rewrite
Writing Testable Code in Magento 1 and 2 - ...
Back to the example code...
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
One other subtle thing here makes testing easier:
public function customerLogin(Event $event)
{
if (! $this->isModuleActiv...
No magic method __call() calls!
// Old "magic" code:
$event->getCustomer();
// New code:
$event->getData('customer')
Writi...
Why does that
improve testability?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Creating a mock with
magic methods is ugly!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vin...
Lots of setup code in tests
distracts
from the important parts.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10...
Noisy test double creation:
$methods = array_merge(
get_class_methods(Event::class),
['getCustomer']
);
$mockEvent = $this...
Much nicer:
$mockEvent = $this->createMock(Event::class);
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - ...
Summary
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
What makes code
simple to test?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
→ Separation of
Business Logic
from
Entry Points
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter:/...
→ Small classes
(and methods)
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
→ Encapsulation of
Business Logic in
specific classes
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitt...
→ Delegation to
injectable dependencies
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKo...
→ Favoring
real methods
over
magic methods
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vina...
Is there more?
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Yes!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
→ Adherence to the Law of Demeter.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
→ Separation of methods that causes
side effects from
methods returning values.
Writing Testable Code in Magento 1 and 2 -...
→ Avoidance of method call chaining.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
→ Methods having a single level of detail.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@Vina...
Lets keep these for another time.
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
So most importantly...
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
...have fun writing tests!
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
(tell you me comment)
(ask? you me question)
(thank you)
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - t...
http://mage2katas.com/
Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
Upcoming SlideShare
Loading in …5
×

Writing Testable Code (for Magento 1 and 2) 2016 Romaina

1,305 views

Published on

The latest iteration of my "Writing testable code" presentation from Meet-Magento Romaina 2016 in Cluj-Napoca.
It covers basics on what properties of production code make testing simpler.

Published in: Software

Writing Testable Code (for Magento 1 and 2) 2016 Romaina

  1. 1. Writing Testable Code (for Magento 1 and 2) Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  2. 2. Assumptions! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  3. 3. You know basic PHPUnit. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  4. 4. You want → Confidence in deploys → Experience joy when writing tests → Have fun doing code maintaince → Get more $$$ out of testing Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  5. 5. In short, you want → Testable code Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  6. 6. When is code "testable"? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  7. 7. When testing is simple & easy. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  8. 8. What makes a test simple? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  9. 9. It is simple to write. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  10. 10. It is easy to read. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  11. 11. What does "easy to read" mean? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  12. 12. It's intent is clear. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  13. 13. The test is short. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  14. 14. Good names. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  15. 15. It only does one thing. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  16. 16. Clean Code is for Production Code & Test Code Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  17. 17. What does "simple to write" mean? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  18. 18. Test code depends on production code Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  19. 19. It are properties of the production code that make testing easy or hard. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  20. 20. What does easy to test code look like? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  21. 21. Magento 1 Example: Event Observer (Legacy Code) Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  22. 22. <?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 public function actionPostdispatchCustomerAccountResetPasswordPost(Event $event) {...} } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  23. 23. Are we going to write Unit Tests? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  24. 24. Hell NO! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  25. 25. Unit tests only provide value for new code. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  26. 26. For previously untested code, Integration Tests are much more valuable. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  27. 27. What would make it simpler to test? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  28. 28. If the class where smallerit would be simpler to test. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  29. 29. First attempt: Splitting the class based on purpose. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  30. 30. What does the class do? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  31. 31. 1. Prevents inactive customer logins. 2. Sends notification emails. 3. Adds a column to the customer grid. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  32. 32. Lets split it into Netzarbeiter_CustomerActivation_Model... ..._Observer_ProhibitInactiveLogins ..._Observer_EmailNotifications ..._Observer_AdminhtmlCustomerGrid Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  33. 33. <?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 causing a logout public function actionPostdispatchCustomerAccountResetPasswordPost(Event $event) {...} } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  34. 34. <?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 in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  35. 35. <?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 in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  36. 36. Is this simpler to test? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  37. 37. Only minor difference in testing effort. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  38. 38. Why? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  39. 39. The same tests as before. Only split into three classes. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  40. 40. Second attempt: Lets go beyond superficial changes. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  41. 41. Lets look at the design. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  42. 42. What collaborators are used? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  43. 43. Collaborators: Netzarbeiter_CustomerActivation_Helper_Data Mage_Customer_Model_Customer Mage_Customer_Model_Session Mage_Customer_Model_Group Mage_Customer_Helper_Address Mage_Customer_Model_Resource_Customer_Collection Mage_Core_Controller_Request_Http Mage_Core_Controller_Response_Http Mage_Core_Exception Mage_Core_Model_Session Mage_Core_Model_Store Mage_Sales_Model_Quote_Address Mage_Sales_Model_Quote Mage_Eav_Model_Config Mage_Eav_Model_Entity_Type Mage_Adminhtml_Block_Widget_Grid_Massaction Mage_Adminhtml_Block_Widget_Grid Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  44. 44. Almost all of them are core classes. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  45. 45. Only two classes are part of the module: Netzarbeiter_CustomerActivation_Model_Observer Netzarbeiter_CustomerActivation_Helper_Data Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  46. 46. Based on the names, why do these classes exist? Netzarbeiter_CustomerActivation_Model_Observer Netzarbeiter_CustomerActivation_Helper_Data Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  47. 47. The names don't tell us anything. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  48. 48. Extract parts by giving them meaningful names Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  49. 49. But where to start? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  50. 50. Separate business logic from entry points. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  51. 51. Entry points are the places Magento provides for our custom code. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  52. 52. Entry points: → Observers → Plugins → Controllers → Cron Jobs → Preferences → Console Commands Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  53. 53. Entry points link Business logic ! Magento Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  54. 54. Remove all Business Logic from Entry Points. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  55. 55. What are the benefits? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  56. 56. For testing: The custom code can be triggered independently of the entry point. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  57. 57. In our example, what is the entry point? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  58. 58. Observer Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  59. 59. Old Observer Code: Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  60. 60. 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 in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  61. 61. New Code, without business logic: Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  62. 62. public function customerLogin(Event $event) { if (! $this->isModuleActive()) { return; } $this->getCustomerLoginSentry()->abortLoginIfNotActive( $event->getData('customer') ); } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  63. 63. And this class is simpler to test? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  64. 64. Yes, as there is much less logic. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  65. 65. Most of the logic is delegated to collaborators. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  66. 66. $this->getCustomerLoginSentry()->abortLoginIfNotActive( $event->getData('customer') ); Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  67. 67. How does the delegation look in detail? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  68. 68. private static $sentryClass = 'customeractivation/customerLoginSentry'; /** * @return CustomerLoginSentry */ private function getCustomerLoginSentry() { return $this->loginSentry ?? Mage::getModel(self::$sentryClass); } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  69. 69. The login sentry can be injected. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  70. 70. That means, it can be replaced by a test double. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  71. 71. Dependency Injection Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  72. 72. DI is a Magento 2 thing, right? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  73. 73. DI can be everywhere! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  74. 74. Injecting Test Doubles in Magento 1 Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  75. 75. Setter Injection public function testDelegatesToLoginSentry() { $mockLoginSentry = $this->createMock(LoginSentry::class); $mockLoginSentry->expects($this->once()) ->method('abortLoginIfNotActive'); $observer = new Netzarbeiter_CustomerActivation_Model_Observer(); $observer->loginSentry = $mockLoginSentry; // ... } Problem: It muddies intention revealing class interfaces. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  76. 76. Constructor Injection public function testDelegatesToLoginSentry() { $mockLoginSentry = $this->createMock(LoginSentry::class); $mockLoginSentry->expects($this->once()) ->method('abortLoginIfNotActive'); $observer = new Netzarbeiter_CustomerActivation_Model_Observer( $mockLoginSentry ); // ... } Problem: Standard Magento 1 instantiation can't do it. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  77. 77. Ugly but hey it works. /** * @param LoginSentry $loginSentry */ public function __construct($loginSentry = null) { $this->loginSentry = $loginSentry; } // ... private function getCustomerLoginSentry() { return $this->loginSentry ?? Mage::getModel(self::$sentry); } Paradoxical: Optional Dependency Injection..? o_O Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  78. 78. Injected collaborators make for simple tests! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  79. 79. Delegation allow us to create classes with a specific purpose. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  80. 80. We can give descriptive names. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  81. 81. Model with one responsibility: class Netzarbeiter_CustomerActivation_Model_CustomerLoginSentry Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  82. 82. public function abortLoginIfNotActive( Mage_Customer_Model_Customer $customer ) { if (! $customer->getData('customer_activated')) { $this->getSession()->logout(); $this->getDisplay()->showLoginAbortedMessage(); } } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  83. 83. This business logic is now independent of the entry point. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  84. 84. It can be called from anywhere: → Test → Observer → Controller → Model Rewrite Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  85. 85. Back to the example code... Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  86. 86. One other subtle thing here makes testing easier: public function customerLogin(Event $event) { if (! $this->isModuleActive()) { return; } $this->getCustomerLoginSentry()->abortLoginIfNotActive( $event->getData('customer') ); } Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  87. 87. No magic method __call() calls! // Old "magic" code: $event->getCustomer(); // New code: $event->getData('customer') Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  88. 88. Why does that improve testability? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  89. 89. Creating a mock with magic methods is ugly! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  90. 90. Lots of setup code in tests distracts from the important parts. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  91. 91. Noisy test double creation: $methods = array_merge( get_class_methods(Event::class), ['getCustomer'] ); $mockEvent = $this->getMockBuilder(Event::class) ->setMethods($methods) ->getMock(); Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  92. 92. Much nicer: $mockEvent = $this->createMock(Event::class); Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  93. 93. Summary Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  94. 94. What makes code simple to test? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  95. 95. → Separation of Business Logic from Entry Points Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  96. 96. → Small classes (and methods) Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  97. 97. → Encapsulation of Business Logic in specific classes Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  98. 98. → Delegation to injectable dependencies Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  99. 99. → Favoring real methods over magic methods Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  100. 100. Is there more? Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  101. 101. Yes! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  102. 102. → Adherence to the Law of Demeter. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  103. 103. → Separation of methods that causes side effects from methods returning values. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  104. 104. → Avoidance of method call chaining. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  105. 105. → Methods having a single level of detail. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  106. 106. Lets keep these for another time. Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  107. 107. So most importantly... Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  108. 108. ...have fun writing tests! Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  109. 109. (tell you me comment) (ask? you me question) (thank you) Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp
  110. 110. http://mage2katas.com/ Writing Testable Code in Magento 1 and 2 - #MM16RO 2016-10-28 - twitter://@VinaiKopp

×