Refactoring legacy code guided by tests in
WordPress
WC Rome, 2017/12/16
Luca Tumedei
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 1
About me
—freelance WordPress developer
—been working with Modern Tribe for 3.5 years now
—creator of wp-browser and lover of tests
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 2
Long time no see
<?php
/*
Plugin Name: My New Plugin
Plugin URI: https://example.com
Version: 0.1.0
Description: Really new; from scratch.
Author: Luca Tumedei
Author URI: http://example.com
*/
Starting from scratch is not an option.
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 3
Dramatis personae - Modern Tribe and The Events
Calendar plugin
—Modern Tribe is a remote (and awesome) agency
providing digital products and services
—The Events Calendar is MT's most famous
WordPress plugin
—The Events Calendar is just one of the many free
and premium WordPress plugins MT actively
maintains
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 4
Dramatis personae - MT developers
—there are 21 developers maintaining MT's plugins
—in their history about 100 different developers
worked on the plugins
—the skill, habits, beliefs and mantras of each might
vary...
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 5
Dramatis personae - the users
—The Events Calendar has about 600.000 active
installations
—The Events Calendar is the base for many of
Modern Tribe premium plugins and services
—third-party developers are users too
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 6
Still a work in progress
—this is not about how "we did it"
—this is about how "we are doing it" and what we
found out along the way
—you can see The Events Calendar code on GitHub
and judge for yourself
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 7
Refactoring
"Code refactoring is the process of restructuring
existing computer code, changing the factoring,
without changing its external behavior."
—Wikipedia
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 8
Legacy code
"Michael Feathers introduced a definition of legacy
code as code without tests, which reflects the
perspective of legacy code being difficult to work
with in part due to a lack of automated regression
tests."
—Wikipedia
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 9
Guided by tests
"Write the tests, then change the code. I need
coffee."
—me, on Slack
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 10
Why then?
—the code was not testable as it was
—tests allow to iterate faster
—faster iterations means shipping new features and
bug fixes faster
We win, users win, everybody wins!
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 11
You are already doing TDD
—you modify the code
—you go on a site page and try it out
—you fix it and try again
You are just doing it slowly, inefficiently and in an
under-engineered way.
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 12
Problem - Testing WordPress is impossible
—Codeception and wp-browser are the tools we
chose
—some of the other options include the Core suite,
WordHat, Behat
—at the very minimum you can always do
acceptance testing
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 13
Why Codeception and wp-browser?
—I might have something to do with it
—wp-browser provides Codeception modules
dedicated to WordPress
—Codeception supports all levels of testing:
acceptance, functional, integration and unit
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 14
Our first acceptance test
$I = new AcceptanceTester( $scenario );
$I->am( 'a WordPress site admin' );
$I->wantTo( 'be able to activate The Events Calendar plugin' );
$I->loginAsAdmin();
$I->amOnPluginsPage();
$I->activatePlugin( 'the-events-calendar' );
$I->seePluginActivated( 'the-events-calendar' );
This runs in .6 seconds. Faster than a person, if you
were guessing.
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 15
A second acceptance test for the front-end
$I = new AcceptanceTester( $scenario );
$I->am( 'a calendar visitor' );
$I->wantTo( 'be able to see the calendar' );
$format = 'Y-m-d H:i:s';
$meta = [
'_EventStartDate' => date( $format , strtotime( 'tomorrow 9am' ) ),
'_EventEndDate' => date( $format , strtotime( 'tomorrow 11am' ) ),
];
$I->havePostInDatabase( [
'post_type' => 'tribe_events',
'meta_input' => $meta,
'post_title' => 'Test Event'
] );
$I->amOnPage( '/events' );
$I->see( 'Test Event' );
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 16
The flow
—write tests and run them to make sure current
code passes them
—refactor the code
—run tests and fix the code until tests pass again
Repeat n times as you would do by hand.
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 17
Problem - PHP 5.2 compatibility
—"it cannot be done in PHP 5.2" is a sad excuse
—production code can be 5.2 compatible, test code
does not need to be
—acceptance tests can run against a PHP 5.2
webserver
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 18
Problem - hardcoded file inclusion
—getting hold of one element is nigh impossible due
to a forest of side effects
—there is always an include or require
somewhere
—we wrote our own autoloader but you can use
Composer and xrstf/composer-php52 to generate
your PHP 5.2 compatible autoload file
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 19
Reorganizing files and classes
—we nested folders "by subject"
—we use __ as a folder separator
—some class names are long...
Tribe__Events__REST__V1__Endpoints__...
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 20
A simple autoloader
function my_autoloader( $class ){
if( 0 !== strpos( $class, 'MyPlugin_' ) ){
return;
}
// `MyPlugin_Foo_Bar_MyClass` to `Foo/Bar/MyClass`
$path = str_replace( '_', '/', str_replace( 'My_Plugin_', '', $class ) );
include dirname( __FILE__ ) . '/src/' . $path . '.php';
}
spl_autoload_register( 'my_autoloader' );
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 21
Problem - the Main class
/**
* Main Tribe Events Calendar class.
*/
// Don't load directly
if ( ! defined( 'ABSPATH' ) ) {
die( '-1' );
}
if ( ! class_exists( 'Tribe__Events__Main' ) ) {
/**
* The Events Calendar Class
*
* This is where all the magic happens, the unicorns run wild and the leprechauns use WordPress to schedule events.
*/
class Tribe__Events__Main {
Emphasis on "This is where all the magic happens".
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 22
Bow to your god object!
—the class did everything: from localization to
managing currency symbols
—it was a Petri dish of anti-patterns
—it was tens of thousands lines long (it's still ~5000)
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 23
Naming things is difficult
"There are only two hard things in Computer
Science: cache invalidation and naming things."
-- Phil Karlton
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 24
Naming things is important
—names are only useful for people, the server does
not care if your classes are called Francine and
Waldo
—names indicate what something is supposed to do
—adding yet another method to Main is easier than
adding it to
Admin_Views_Single_Event_Metabox
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 25
Reducing Main
—break it down into smaller, specialized, classes
—STOP. ADDING. METHODS. TO. IT.
—create subsidiary "Mains" and stop calling things
"Main"
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 26
Deprecating files, classes and hooks
—we moved a lot of things during the process of
refactoring
—we used __deprecated_file(),
__deprecated_function() and
__deprecated_hook() extensively in our code
—we tried to avoid breaking compatibility 99% of
the times
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 27
Problem - it's never time to refactor
"That thing developers do that costs money."
—any Project Manager
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 28
Guerrilla refactoring
—leave the code a little better than you found it
—refactoring and writing tests should be part of
every estimate
—tests should be in the same PR as the code, lead
developers should enforce it
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 29
Filling in the blanks
Developer 1 writes this:
function test_multi(){
$this->assertEquals( 4, func( 2 ) );
$this->assertEquals( 4, func( 2, 2 ) );
$this->assertEquals( 6, func( 2, 3 ) );
$this->assertEquals( 0, func( 0, 3 ) );
}
Developer 2 writes the func() code to make the
tests pass.
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 30
Using a CI to run the tests
—we use Travis CI, there are many others
—tests will run on each PR with visible results that
can prevent PRs from being merged
—removes the "I cannot run the tests" excuse
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 31
Some random good things we did
—lead by example
—test setup parties (nerds rejoice)
—discuss code, not theories, patterns and best
practices in dev meetings
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 32
Takeaways - 1
—it's about changing the people, not the code
—in the long run you want better developers, not
better code
—better developers write better code; writing good
code is an habit, not a function of time
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 33
Takeaways - 2
—do it for the time (and money) you will save, not
for the code
—do it to release more often
—use a CI system
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 34
Takeaways - 3
—start with an acceptance test
—do not separate code and tests in PRs and/or
issue trackers
—let go of the code coverage metrics
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 35
Questions?
Thanks for listening to me, questions?
Are you a good person? Apply to work at Modern
Tribe: tri.be/careers/
@lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 36

Refactoring legacy code guided by tests in WordPress

  • 1.
    Refactoring legacy codeguided by tests in WordPress WC Rome, 2017/12/16 Luca Tumedei @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 1
  • 2.
    About me —freelance WordPressdeveloper —been working with Modern Tribe for 3.5 years now —creator of wp-browser and lover of tests @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 2
  • 3.
    Long time nosee <?php /* Plugin Name: My New Plugin Plugin URI: https://example.com Version: 0.1.0 Description: Really new; from scratch. Author: Luca Tumedei Author URI: http://example.com */ Starting from scratch is not an option. @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 3
  • 4.
    Dramatis personae -Modern Tribe and The Events Calendar plugin —Modern Tribe is a remote (and awesome) agency providing digital products and services —The Events Calendar is MT's most famous WordPress plugin —The Events Calendar is just one of the many free and premium WordPress plugins MT actively maintains @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 4
  • 5.
    Dramatis personae -MT developers —there are 21 developers maintaining MT's plugins —in their history about 100 different developers worked on the plugins —the skill, habits, beliefs and mantras of each might vary... @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 5
  • 6.
    Dramatis personae -the users —The Events Calendar has about 600.000 active installations —The Events Calendar is the base for many of Modern Tribe premium plugins and services —third-party developers are users too @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 6
  • 7.
    Still a workin progress —this is not about how "we did it" —this is about how "we are doing it" and what we found out along the way —you can see The Events Calendar code on GitHub and judge for yourself @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 7
  • 8.
    Refactoring "Code refactoring isthe process of restructuring existing computer code, changing the factoring, without changing its external behavior." —Wikipedia @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 8
  • 9.
    Legacy code "Michael Feathersintroduced a definition of legacy code as code without tests, which reflects the perspective of legacy code being difficult to work with in part due to a lack of automated regression tests." —Wikipedia @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 9
  • 10.
    Guided by tests "Writethe tests, then change the code. I need coffee." —me, on Slack @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 10
  • 11.
    Why then? —the codewas not testable as it was —tests allow to iterate faster —faster iterations means shipping new features and bug fixes faster We win, users win, everybody wins! @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 11
  • 12.
    You are alreadydoing TDD —you modify the code —you go on a site page and try it out —you fix it and try again You are just doing it slowly, inefficiently and in an under-engineered way. @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 12
  • 13.
    Problem - TestingWordPress is impossible —Codeception and wp-browser are the tools we chose —some of the other options include the Core suite, WordHat, Behat —at the very minimum you can always do acceptance testing @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 13
  • 14.
    Why Codeception andwp-browser? —I might have something to do with it —wp-browser provides Codeception modules dedicated to WordPress —Codeception supports all levels of testing: acceptance, functional, integration and unit @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 14
  • 15.
    Our first acceptancetest $I = new AcceptanceTester( $scenario ); $I->am( 'a WordPress site admin' ); $I->wantTo( 'be able to activate The Events Calendar plugin' ); $I->loginAsAdmin(); $I->amOnPluginsPage(); $I->activatePlugin( 'the-events-calendar' ); $I->seePluginActivated( 'the-events-calendar' ); This runs in .6 seconds. Faster than a person, if you were guessing. @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 15
  • 16.
    A second acceptancetest for the front-end $I = new AcceptanceTester( $scenario ); $I->am( 'a calendar visitor' ); $I->wantTo( 'be able to see the calendar' ); $format = 'Y-m-d H:i:s'; $meta = [ '_EventStartDate' => date( $format , strtotime( 'tomorrow 9am' ) ), '_EventEndDate' => date( $format , strtotime( 'tomorrow 11am' ) ), ]; $I->havePostInDatabase( [ 'post_type' => 'tribe_events', 'meta_input' => $meta, 'post_title' => 'Test Event' ] ); $I->amOnPage( '/events' ); $I->see( 'Test Event' ); @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 16
  • 17.
    The flow —write testsand run them to make sure current code passes them —refactor the code —run tests and fix the code until tests pass again Repeat n times as you would do by hand. @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 17
  • 18.
    Problem - PHP5.2 compatibility —"it cannot be done in PHP 5.2" is a sad excuse —production code can be 5.2 compatible, test code does not need to be —acceptance tests can run against a PHP 5.2 webserver @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 18
  • 19.
    Problem - hardcodedfile inclusion —getting hold of one element is nigh impossible due to a forest of side effects —there is always an include or require somewhere —we wrote our own autoloader but you can use Composer and xrstf/composer-php52 to generate your PHP 5.2 compatible autoload file @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 19
  • 20.
    Reorganizing files andclasses —we nested folders "by subject" —we use __ as a folder separator —some class names are long... Tribe__Events__REST__V1__Endpoints__... @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 20
  • 21.
    A simple autoloader functionmy_autoloader( $class ){ if( 0 !== strpos( $class, 'MyPlugin_' ) ){ return; } // `MyPlugin_Foo_Bar_MyClass` to `Foo/Bar/MyClass` $path = str_replace( '_', '/', str_replace( 'My_Plugin_', '', $class ) ); include dirname( __FILE__ ) . '/src/' . $path . '.php'; } spl_autoload_register( 'my_autoloader' ); @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 21
  • 22.
    Problem - theMain class /** * Main Tribe Events Calendar class. */ // Don't load directly if ( ! defined( 'ABSPATH' ) ) { die( '-1' ); } if ( ! class_exists( 'Tribe__Events__Main' ) ) { /** * The Events Calendar Class * * This is where all the magic happens, the unicorns run wild and the leprechauns use WordPress to schedule events. */ class Tribe__Events__Main { Emphasis on "This is where all the magic happens". @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 22
  • 23.
    Bow to yourgod object! —the class did everything: from localization to managing currency symbols —it was a Petri dish of anti-patterns —it was tens of thousands lines long (it's still ~5000) @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 23
  • 24.
    Naming things isdifficult "There are only two hard things in Computer Science: cache invalidation and naming things." -- Phil Karlton @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 24
  • 25.
    Naming things isimportant —names are only useful for people, the server does not care if your classes are called Francine and Waldo —names indicate what something is supposed to do —adding yet another method to Main is easier than adding it to Admin_Views_Single_Event_Metabox @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 25
  • 26.
    Reducing Main —break itdown into smaller, specialized, classes —STOP. ADDING. METHODS. TO. IT. —create subsidiary "Mains" and stop calling things "Main" @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 26
  • 27.
    Deprecating files, classesand hooks —we moved a lot of things during the process of refactoring —we used __deprecated_file(), __deprecated_function() and __deprecated_hook() extensively in our code —we tried to avoid breaking compatibility 99% of the times @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 27
  • 28.
    Problem - it'snever time to refactor "That thing developers do that costs money." —any Project Manager @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 28
  • 29.
    Guerrilla refactoring —leave thecode a little better than you found it —refactoring and writing tests should be part of every estimate —tests should be in the same PR as the code, lead developers should enforce it @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 29
  • 30.
    Filling in theblanks Developer 1 writes this: function test_multi(){ $this->assertEquals( 4, func( 2 ) ); $this->assertEquals( 4, func( 2, 2 ) ); $this->assertEquals( 6, func( 2, 3 ) ); $this->assertEquals( 0, func( 0, 3 ) ); } Developer 2 writes the func() code to make the tests pass. @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 30
  • 31.
    Using a CIto run the tests —we use Travis CI, there are many others —tests will run on each PR with visible results that can prevent PRs from being merged —removes the "I cannot run the tests" excuse @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 31
  • 32.
    Some random goodthings we did —lead by example —test setup parties (nerds rejoice) —discuss code, not theories, patterns and best practices in dev meetings @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 32
  • 33.
    Takeaways - 1 —it'sabout changing the people, not the code —in the long run you want better developers, not better code —better developers write better code; writing good code is an habit, not a function of time @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 33
  • 34.
    Takeaways - 2 —doit for the time (and money) you will save, not for the code —do it to release more often —use a CI system @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 34
  • 35.
    Takeaways - 3 —startwith an acceptance test —do not separate code and tests in PRs and/or issue trackers —let go of the code coverage metrics @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 35
  • 36.
    Questions? Thanks for listeningto me, questions? Are you a good person? Apply to work at Modern Tribe: tri.be/careers/ @lucatume - theaveragedev.com - Modern Tribe - The Events Calendar 36