Slide 1

Slide 1 text

unit testing Introduction to WordPress

Slide 2

Slide 2 text

Carl Alexander

Slide 3

Slide 3 text

@twigpress

Slide 4

Slide 4 text

carlalexander.ca

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

This code worked the other day

Slide 7

Slide 7 text

You need to protect yourself

Slide 8

Slide 8 text

Say hello to testing!

Slide 9

Slide 9 text

testing is a HUGE field

Slide 10

Slide 10 text

Focus on unit testing

Slide 11

Slide 11 text

What is unit testing?

Slide 12

Slide 12 text

Testing at the smallest scale

Slide 13

Slide 13 text

Input values into a function

Slide 14

Slide 14 text

Check what comes out at the end

Slide 15

Slide 15 text

Goal: Constant behaviour

Slide 16

Slide 16 text

Goal: Safety net

Slide 17

Slide 17 text

Goal: Code quality

Slide 18

Slide 18 text

in unit testing Isolation

Slide 19

Slide 19 text

Cornerstone of unit testing

Slide 20

Slide 20 text

Done using test doubles

Slide 21

Slide 21 text

Magical machines surrounding your code

Slide 22

Slide 22 text

Gives you ABSOLUTE control

Slide 23

Slide 23 text

What is my code doing?

Slide 24

Slide 24 text

Not what is WordPress doing?

Slide 25

Slide 25 text

What makes testable code higher quality?

Slide 26

Slide 26 text

Cyclomatic complexity

Slide 27

Slide 27 text

Maps unique paths through your code

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Code with more paths is harder to test

Slide 30

Slide 30 text

Makes you write smaller, less complex functions

Slide 31

Slide 31 text

WordPress tests unit tests VS

Slide 32

Slide 32 text

WordPress tests are integration tests

Slide 33

Slide 33 text

Different relationship with isolation

Slide 34

Slide 34 text

It’s about how pieces of code work together

Slide 35

Slide 35 text

Isolating a piece of code isn’t as necessary

Slide 36

Slide 36 text

Neither is better

Slide 37

Slide 37 text

Ideally, you do both

Slide 38

Slide 38 text

Questions?

Slide 39

Slide 39 text

The unit testing setup

Slide 40

Slide 40 text

Operating System: unix-based

Slide 41

Slide 41 text

PHP version: 5.3 (for code) / 5.4 (for tests)

Slide 42

Slide 42 text

Composer

Slide 43

Slide 43 text

PHPUnit

Slide 44

Slide 44 text

WP-CLI (optional)

Slide 45

Slide 45 text

Creating our 
 unit tested plugin

Slide 46

Slide 46 text

wp  scaffold  plugin  unit-­‐tested-­‐plugin

Slide 47

Slide 47 text

Add composer.json

Slide 48

Slide 48 text

{
        "name":  "carlalexander/unit-­‐tested-­‐plugin",
        "type":  "project",
        "description":  "A  unit  tested  WordPress  plugin",
        "homepage":  "https://carlalexander.ca",
        "license":  "GPL-­‐3.0+",
        "require":  {
                "php":  ">=5.3.0"
        },
        "require-­‐dev":  {
                "php-­‐mock/php-­‐mock-­‐phpunit":  "^0.3"
        }
 }

Slide 49

Slide 49 text

{
        "name":  "carlalexander/unit-­‐tested-­‐plugin",
        "type":  "project",
        "description":  "A  unit  tested  WordPress  plugin",
        "homepage":  "https://carlalexander.ca",
        "license":  "GPL-­‐3.0+",
        "require":  {
                "php":  ">=5.3.0"
        },
        "require-­‐dev":  {
                "php-­‐mock/php-­‐mock-­‐phpunit":  "^0.3"
        }
 }

Slide 50

Slide 50 text

{
        "name":  "carlalexander/unit-­‐tested-­‐plugin",
        "type":  "project",
        "description":  "A  unit  tested  WordPress  plugin",
        "homepage":  "https://carlalexander.ca",
        "license":  "GPL-­‐3.0+",
        "require":  {
                "php":  ">=5.3.0"
        },
        "require-­‐dev":  {
                "php-­‐mock/php-­‐mock-­‐phpunit":  "^0.3"
        }
 }

Slide 51

Slide 51 text

Modify bootstrap.php (created by WP-CLI)

Slide 52

Slide 52 text

$_tests_dir  =  getenv('WP_TESTS_DIR');
 if  (  !$_tests_dir  )  $_tests_dir  =  '/tmp/wordpress-­‐tests-­‐lib';
 
 require_once  $_tests_dir  .  '/includes/functions.php';
 
 function  _manually_load_plugin()  {
   require  dirname(  __FILE__  )  .  '/../unit-­‐tested-­‐plugin.php';
 }
 tests_add_filter(  'muplugins_loaded',  '_manually_load_plugin'  );
 
 require  $_tests_dir  .  '/includes/bootstrap.php';

Slide 53

Slide 53 text

Composer missing

Slide 54

Slide 54 text

require_once(dirname(__DIR__)  .  '/vendor/autoload.php');
 
 $_tests_dir  =  getenv('WP_TESTS_DIR');
 if  (  !$_tests_dir  )  $_tests_dir  =  '/tmp/wordpress-­‐tests-­‐lib';
 
 require_once  $_tests_dir  .  '/includes/functions.php';
 
 function  _manually_load_plugin()  {
   require  dirname(  __FILE__  )  .  '/../unit-­‐tested-­‐plugin.php';
 }
 tests_add_filter(  'muplugins_loaded',  '_manually_load_plugin'  );
 
 require  $_tests_dir  .  '/includes/bootstrap.php';

Slide 55

Slide 55 text

Our first plugin function

Slide 56

Slide 56 text

namespace  UnitTestDemo;
 /**
  *  Get  a  plugin  option  from  the  WordPress  database.
  *
  *  @param  string  $name
  *
  *  @return  mixed
  */
 function  demo_get_option($name)
 {
        return  get_option('demo_'  .  $name);
 }  

Slide 57

Slide 57 text

namespace  UnitTestDemo;
 /**
  *  Get  a  plugin  option  from  the  WordPress  database.
  *
  *  @param  string  $name
  *
  *  @return  mixed
  */
 function  demo_get_option($name)
 {
        return  get_option('demo_'  .  $name);
 }  

Slide 58

Slide 58 text

namespace  UnitTestDemo;
 /**
  *  Get  a  plugin  option  from  the  WordPress  database.
  *
  *  @param  string  $name
  *
  *  @return  mixed
  */
 function  demo_get_option($name)
 {
        return  get_option('demo_'  .  $name);
 }  

Slide 59

Slide 59 text

Questions?

Slide 60

Slide 60 text

Our first unit test

Slide 61

Slide 61 text

namespace  UnitTestDemo;
 
 use  phpmock\phpunit\PHPMock;
 
 class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        use  PHPMock;          //  ...   }

Slide 62

Slide 62 text

namespace  UnitTestDemo;
 
 use  phpmock\phpunit\PHPMock;
 
 class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        use  PHPMock;          //  ...   }

Slide 63

Slide 63 text

namespace  UnitTestDemo;
 
 use  phpmock\phpunit\PHPMock;
 
 class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        use  PHPMock;          //  ...   }

Slide 64

Slide 64 text

namespace  UnitTestDemo;
 
 use  phpmock\phpunit\PHPMock;
 
 class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        use  PHPMock;          //  ...   }

Slide 65

Slide 65 text

namespace  UnitTestDemo;
 
 use  phpmock\phpunit\PHPMock;
 
 class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        use  PHPMock;          //  ...   }

Slide 66

Slide 66 text

Testing demo_get_option

Slide 67

Slide 67 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 68

Slide 68 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 69

Slide 69 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 70

Slide 70 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 71

Slide 71 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 72

Slide 72 text

Let's try to run it

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

Oops, that didn’t work! (Why is that?)

Slide 75

Slide 75 text

Missing test double

Slide 76

Slide 76 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 77

Slide 77 text

What’s a mock?

Slide 78

Slide 78 text

Type of test double

Slide 79

Slide 79 text

Simulates the behaviour of a function or object

Slide 80

Slide 80 text

Verifies how you interact with it

Slide 81

Slide 81 text

Only type of test double that does that

Slide 82

Slide 82 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with($this-­‐>equalTo('demo_foo'))
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 83

Slide 83 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with($this-­‐>equalTo('demo_foo'))
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 84

Slide 84 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with($this-­‐>equalTo('demo_foo'))
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 85

Slide 85 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with($this-­‐>equalTo('demo_foo'))
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

Our first passing test!
 (Yay!)

Slide 88

Slide 88 text

Questions?

Slide 89

Slide 89 text

Pushing things further

Slide 90

Slide 90 text

Common to store an array inside an option

Slide 91

Slide 91 text

Except you might not always get an array back

Slide 92

Slide 92 text

You’ll often see (array) next to get_option

Slide 93

Slide 93 text

Let’s make our function do this for us

Slide 94

Slide 94 text

But let’s do it in reverse
 (plot twist!)

Slide 95

Slide 95 text

Start with a failing test

Slide 96

Slide 96 text

Gives you a taste for test-driven development

Slide 97

Slide 97 text

First, a small change to demo_get_option

Slide 98

Slide 98 text

namespace  UnitTestDemo;
 /**
  *  Get  a  plugin  option  from  the  WordPress  database.
  *
  *  @param  string  $name
  *  @param  mixed    $default
  *
  *  @return  mixed
  */
 function  demo_get_option($name,  $default  =  null)
 {
        return  get_option('demo_'  .  $name,  $default);
 }  

Slide 99

Slide 99 text

Update our test as well

Slide 100

Slide 100 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with(                                                      $this-­‐>equalTo('demo_foo'),                                                      $this-­‐>identicalTo(null)                                        )
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals('bar',  demo_get_option('foo'));
        }   }

Slide 101

Slide 101 text

identicalTo() equalTo() VS

Slide 102

Slide 102 text

Creating our failing test

Slide 103

Slide 103 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option_casts_array()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with(                                                      $this-­‐>equalTo('demo_foo'),                                                      $this-­‐>identicalTo(array())                                        )
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals(
                        array('bar'),  demo_get_option('foo',  array())
                );
        }   }

Slide 104

Slide 104 text

Similar to our other test

Slide 105

Slide 105 text

Changing inputs, outputs and expected values

Slide 106

Slide 106 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option_casts_array()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with(                                                      $this-­‐>equalTo('demo_foo'),                                                      $this-­‐>identicalTo(array())                                        )
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals(
                        array('bar'),  demo_get_option('foo',  array())
                );
        }   }

Slide 107

Slide 107 text

class  DemoTest  extends  \PHPUnit_Framework_TestCase
 {
        public  function  test_demo_get_option_casts_array()
        {                  $get_option  =  $this-­‐>getFunctionMock(                          'UnitTestDemo',  'get_option'                  );
                $get_option-­‐>expects($this-­‐>once())
                                      -­‐>with(                                                      $this-­‐>equalTo('demo_foo'),                                                      $this-­‐>identicalTo(array())                                        )
                                      -­‐>willReturn('bar');   
                $this-­‐>assertEquals(
                        array('bar'),  demo_get_option('foo',  array())
                );
        }   }

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

Let’s get the test to pass

Slide 110

Slide 110 text

namespace  UnitTestDemo;
 
 /**
  *  Get  a  plugin  option  from  the  WordPress  database.
  *
  *  @param  string  $name
  *  @param  mixed    $default
  *
  *  @return  mixed
  */
 function  demo_get_option($name,  $default  =  null)
 {
        $option  =  get_option('demo_'  .  $name,  $default);
 
        if  (is_array($default)  &&  !is_array($option))  {
                $option  =  (array)  $option;
        }
 
        return  $option;
 }

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

And there you go!

Slide 113

Slide 113 text

Questions?

Slide 114

Slide 114 text

Building your testing habit

Slide 115

Slide 115 text

A test, by itself, feels pretty insignificant

Slide 116

Slide 116 text

Just one loop in your safety net

Slide 117

Slide 117 text

You need more than one for them to support you

Slide 118

Slide 118 text

Testing is a lot like picking up a new habit

Slide 119

Slide 119 text

Hard to stay motivated (Chips are soooo tasty!)

Slide 120

Slide 120 text

https://github.com/carlalexander/wordpress-unit-test-demo Ready to start?

Slide 121

Slide 121 text

Thank you!