Slide 1

Slide 1 text

@mheap #dpc18 for characterization tests

Slide 2

Slide 2 text

@mheap #dpc18 Imagine this…

Slide 3

Slide 3 text

@mheap #dpc18 $$$

Slide 4

Slide 4 text

@mheap #dpc18 $$$ $$$ $$$

Slide 5

Slide 5 text

@mheap #dpc18 $$$ Billing Rating Auth Report Masking Encoding $$$ $$$

Slide 6

Slide 6 text

@mheap #dpc18 $$$ Billing Rating Auth Report Masking Encoding $$$ $$$

Slide 7

Slide 7 text

@mheap #dpc18 $$$ $$$ $$$ Billing Rating Auth Report Masking Encoding public function provides() { return [Client ::class]; }

Slide 8

Slide 8 text

@mheap #dpc18

Slide 9

Slide 9 text

@mheap #dpc18 $ ls -l test total 0

Slide 10

Slide 10 text

@mheap #dpc18 YOU ARE IN A LEGACY CODEBASE > RUN TESTS YOU HAVE NO TESTS > READ SPEC YOU HAVE NO SPEC > WRITE FIX YOU ARE EATEN BY AN ELDER CODE HACK.

Slide 11

Slide 11 text

@mheap #dpc18 Imagine

Slide 12

Slide 12 text

@mheap #dpc18 Hello, I’m Michael

Slide 13

Slide 13 text

@mheap #dpc18 @mheap

Slide 14

Slide 14 text

@mheap #dpc18

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

@mheap #dpc18 Today

Slide 17

Slide 17 text

@mheap #dpc18 What is legacy code?

Slide 18

Slide 18 text

@mheap #dpc18 “code without tests” Legacy Code is

Slide 19

Slide 19 text

@mheap #dpc18 Legacy Code is “valuable code that we feel afraid to change” JB Rainsberger - Surviving Legacy Code with Golden Master and Sampling

Slide 20

Slide 20 text

@mheap #dpc18 What if I make a change and break something?

Slide 21

Slide 21 text

@mheap #dpc18 What if I make a change and change something?

Slide 22

Slide 22 text

@mheap #dpc18 Intended or Unintended

Slide 23

Slide 23 text

@mheap #dpc18 Intended or Unintended

Slide 24

Slide 24 text

@mheap #dpc18 Intended or Unintended

Slide 25

Slide 25 text

@mheap #dpc18 But there aren’t any tests!

Slide 26

Slide 26 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 27

Slide 27 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 28

Slide 28 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 29

Slide 29 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 30

Slide 30 text

@mheap #dpc18 use PHPUnit\Framework\TestCase; class SalesUtilTest extends TestCase { public function testX() { $this ->assertEquals(null, SalesUtil ::calculate(1000)); } }

Slide 31

Slide 31 text

@mheap #dpc18 use PHPUnit\Framework\TestCase; class SalesUtilTest extends TestCase { public function testX() { $this ->assertEquals(null, SalesUtil ::calculate(999)); } }

Slide 32

Slide 32 text

@mheap #dpc18 use PHPUnit\Framework\TestCase; class SalesUtilTest extends TestCase { public function testX() { $this ->assertEquals(null, SalesUtil ::calculate(1000)); } }

Slide 33

Slide 33 text

@mheap #dpc18 There was 1 failure: 1) SalesUtilTest ::testX Failed asserting that 200 matches expected null. /Users/michael/development/oss/characterisation-tests-examples/sales-util/test/SalesUtilTest.php:8 FAILURES! Tests: 1, Assertions: 1, Failures: 1.

Slide 34

Slide 34 text

@mheap #dpc18 use PHPUnit\Framework\TestCase; class SalesUtilTest extends TestCase { public function testSalesLTE1000Pay20PercentCommission() { $this ->assertEquals(200, SalesUtil ::calculate(1000)); } }

Slide 35

Slide 35 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 36

Slide 36 text

@mheap #dpc18 There was 1 failure: 1) SalesUtilTest ::testX Failed asserting that 500.0 matches expected null. /Users/michael/development/oss/characterisation-tests-examples/sales-util/test/SalesUtilTest.php:13 FAILURES! Tests: 2, Assertions: 2, Failures: 1.

Slide 37

Slide 37 text

@mheap #dpc18 public function test2000Gets500Commission() { $this ->assertEquals(500, SalesUtil ::calculate(2000)); } public function testX() { $this ->assertEquals(null, SalesUtil ::calculate(1600)); }

Slide 38

Slide 38 text

@mheap #dpc18 There was 1 failure: 1) SalesUtilTest ::testX Failed asserting that 380.0 matches expected null. /Users/michael/development/oss/characterisation-tests-examples/sales-util/test/SalesUtilTest.php:19 FAILURES! Tests: 3, Assertions: 3, Failures: 1.

Slide 39

Slide 39 text

@mheap #dpc18 class SalesUtil { const BQ = 1000.0; const BCR = 0.20; const OQM1 = 1.5; const OQM2 = self ::OQM1 * 2; static public function calculate($tSales) { if ($tSales <= static ::BQ) { return $tSales * static ::BCR; } else if ($tSales <= static ::BQ*2) { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1; } else { return static ::BQ * static ::BCR + ($tSales - static ::BQ) * static ::BCR * static ::OQM1 + ($tSales - static ::BQ * 2) * static ::BCR * static ::OQM2; } } }

Slide 40

Slide 40 text

@mheap #dpc18 Input Expected 0 0 10 2 500 100 1000 200 1001 200.3 1500 350 1982 494.6 1999.9 499.97 2000 500 2001 500.9 5000 3200 7890 5801 10000 7700

Slide 41

Slide 41 text

@mheap #dpc18 public function testX() { $this ->assertEquals(null, SalesUtil ::calculate(-123)); }

Slide 42

Slide 42 text

@mheap #dpc18 public function testNegativeSalesCommission() { $this ->assertEquals(-24.6, SalesUtil ::calculate(-123)); }

Slide 43

Slide 43 text

@mheap #dpc18 Do not change the code

Slide 44

Slide 44 text

@mheap #dpc18 Do not change the code

Slide 45

Slide 45 text

@mheap #dpc18 Do NOT change the code

Slide 46

Slide 46 text

@mheap https://en.wikipedia.org/wiki/Equivalence_partitioning What’s the minimum number of tests?

Slide 47

Slide 47 text

@mheap #dpc18 Using Behat

Slide 48

Slide 48 text

@mheap #dpc18 Choosing your pinch points

Slide 49

Slide 49 text

@mheap #dpc18 Choose HTTP

Slide 50

Slide 50 text

@mheap #dpc18 Choose Behat

Slide 51

Slide 51 text

@mheap #dpc18 Given I have 1 "Red Bucket" in my basket And I have 2 "Large Spades" in my basket When I add 1 "Red Bucket" in my basket Then I should see 2 "Red Buckets" in my basket And I should see 2 "Large Spades" in my basket

Slide 52

Slide 52 text

@mheap #dpc18 Given I have 1 "Red Bucket" in my basket And I have 2 "Large Spades" in my basket When I add 1 "Red Bucket" in my basket Then I should see 2 "Red Buckets" in my basket And I should see 2 "Large Spades" in my basket

Slide 53

Slide 53 text

@mheap #dpc18 Given I have 1 "Red Bucket" in my basket And I have 2 "Large Spades" in my basket When I add 1 "Red Bucket" in my basket Then I should see 2 "Red Buckets" in my basket And I should see 2 "Large Spades" in my basket

Slide 54

Slide 54 text

@mheap #dpc18 Given I have 1 "Red Bucket" in my basket And I have 2 "Large Spades" in my basket When I add 1 "Red Bucket" in my basket Then I should see 2 "Red Buckets" in my basket And I should see 2 "Large Spades" in my basket

Slide 55

Slide 55 text

@mheap #dpc18 Given I have 1 "Red Bucket" in my basket And I have 2 "Large Spades" in my basket When I add 1 "Red Bucket" in my basket Then I should see 2 "Red Buckets" in my basket And I should see 2 "Large Spades" in my basket

Slide 56

Slide 56 text

@mheap #dpc18 Be pragmatic

Slide 57

Slide 57 text

@mheap #dpc18 composer require behat/behat --dev vendor/bin/behat --init

Slide 58

Slide 58 text

@mheap #dpc18 Testing a HTTP API

Slide 59

Slide 59 text

@mheap #dpc18 $username = $_GET['username'] ?? error('username is required'); $key = $_GET['key'] ?? error('key is required'); $validUsers = [ "michael" => "kangar00s", "oscar" => "phparch17" ]; $validKey = $validUsers[strtolower($username)] ?? error('could not find user', 404); if ($key !== $validKey){ error('invalid apikey'); } output([ 'success' => 'true', 'user' => ['name' => $username] ]);

Slide 60

Slide 60 text

@mheap #dpc18 $username = $_GET['username'] ?? error('username is required'); $key = $_GET['key'] ?? error('key is required'); $validUsers = [ "michael" => "kangar00s", "oscar" => "phparch17" ]; $validKey = $validUsers[strtolower($username)] ?? error('could not find user', 404); if ($key !== $validKey){ error('invalid apikey'); } output([ 'success' => 'true', 'user' => ['name' => $username] ]);

Slide 61

Slide 61 text

@mheap #dpc18 $username = $_GET['username'] ?? error('username is required'); $key = $_GET['key'] ?? error('key is required'); $validUsers = [ "michael" => "kangar00s", "oscar" => "phparch17" ]; $validKey = $validUsers[strtolower($username)] ?? error('could not find user', 404); if ($key !== $validKey){ error('invalid apikey'); } output([ 'success' => 'true', 'user' => ['name' => $username] ]);

Slide 62

Slide 62 text

@mheap #dpc18 error('username is required'); error('key is required'); error('could not find user', 404); error('invalid apikey'); output([ 'success' => 'true', 'user' => ['name' => $username] ]); 1. Missing username 2. Missing key 3. Invalid username 4. Invalid key 5. Successful authentication

Slide 63

Slide 63 text

@mheap #dpc18 Scenario: No Username When I make a "GET" request to "/" Then the response status code should be "400" And the "error" property equals "username is required"

Slide 64

Slide 64 text

@mheap #dpc18 Scenario: No API Key When I make a "GET" request to "/?username=foo" Then the response status code should be "400" And the "error" property equals "key is required”

Slide 65

Slide 65 text

@mheap #dpc18 Scenario: Invalid Username When I make a "GET" request to "/?username=bananas&key=foo" Then the response status code should be "404" And the "error" property equals "could not find user” Scenario: Invalid API Key When I make a "GET" request to "/?username=michael&key=foo" Then the response status code should be "400" And the "error" property equals "invalid apikey"

Slide 66

Slide 66 text

@mheap #dpc18 Scenario: Successful login When I make a "GET" request to "/?username=michael&key=kangar00s" Then the response status code should be "200" And the "success" property equals "true" And the "user.name" property equals "michael”

Slide 67

Slide 67 text

@mheap #dpc18 Scenario Outline: Successful login When I make a "GET" request to "/?username=&key=" Then the response status code should be "200" And the "success" property equals "true" And the "user.name" property equals "" Examples: | name | key | | michael | kangar00s | | oscar | phparch17 |

Slide 68

Slide 68 text

@mheap #dpc18 More complex tests

Slide 69

Slide 69 text

@mheap #dpc18 Given that header property "X-CustomAuthKey" is "Secret123" And that header property "Accept" is "application/json+vnd.v2" And that the request body is valid JSON ''' { "alpha":"beta", "count":3, "collection":["a","b","c"] } ''' When I make a "POST" request to "/account/balance" Then the response status code should be "200" And the "X-Remaining" header property equals "18" And the "balance.usd" property equals"1832.54" And the "balance.gbp" property equals "13.99" And the "balance.eur" property equals "19422.18"

Slide 70

Slide 70 text

@mheap #dpc18 When I make a "POST" request to "/account/balance" Then the response status code should be “200" And the "X-Remaining" header property equals "18" And the response body contains the JSON data ''' { "in_credit": true, "balance": { "usd": 1832.54, "gbp": 13.99, "eur": 19422.18 } } '''

Slide 71

Slide 71 text

@mheap #dpc18 When I make a "POST" request to "/account/balance" Then the response status code should be "200" And the "X-Remaining" header property equals "18" And the value of the "balance.usd" property matches the pattern “/^\d{1,3}\. \d{2}$/" And the value of the “last_updated” property matches the pattern "/^[0-9]{4} [\-][0-9]{2}[\-][0-9]{2} [0-9]{2}[:][0-9]{2}[:][0-9]{2}$/"

Slide 72

Slide 72 text

@mheap #dpc18 composer require datasift/testrest-extension

Slide 73

Slide 73 text

@mheap #dpc18 # behat.yml default: extensions: DataSift\BehatExtension: base_url: "http: //localhost:8080/" suites: default: contexts: - 'DataSift\BehatExtension\Context\RestContext'

Slide 74

Slide 74 text

@mheap #dpc18 HTTP Mocks

Slide 75

Slide 75 text

@mheap #dpc18 function get_balance($username) { return $client ->get( “http: //localhost:88/billing/check_balance/".$username ) ->getBody(); } $balance = get_balance($username); if ($username != 'michael') { error('wrong username'); } if (!$balance['has_credit']) { error('no credit remaining'); } success(['data' => 'valid account'])

Slide 76

Slide 76 text

@mheap #dpc18 Mountebank

Slide 77

Slide 77 text

@mheap #dpc18 npm install -g mountebank

Slide 78

Slide 78 text

@mheap #dpc18 Given Mountebank is running And a mock exists at "/billing/check_balance" it should return "200" with the body: ''' { "has_credit": false, "credit_limit": 0 } ''' And the mocks are created When I make a "GET" request to "/auth?username=michael" Then the response status code should be "403" And the response is JSON And the response body JSON equals ''' { "error": "no credit remaining" } '''

Slide 79

Slide 79 text

@mheap #dpc18 Given Mountebank is running And a mock exists at "/billing/check_balance" it should return "200" with the body: ''' { "has_credit": true, "credit_limit": 10000 } ''' And the mocks are created When I make a "GET" request to "/auth?username=michael" Then the response status code should be "200" And the response is JSON And the response body JSON equals ''' { "data": "valid account" } '''

Slide 80

Slide 80 text

@mheap #dpc18 Automating a browser

Slide 81

Slide 81 text

@mheap #dpc18 $ composer require --dev behat/mink-extension $ composer require --dev behat/mink-selenium2-driver $ java -Dwebdriver.chrome.driver=chromedriver -jar selenium- server-standalone-3.7.1.jar

Slide 82

Slide 82 text

@mheap #dpc18 # behat.yml default: extensions: Behat\MinkExtension: base_url: 'https: //wikipedia.org' sessions: default: selenium2: browser: "chrome" suites: my_suite: contexts: - \Behat\MinkExtension\Context\MinkContext

Slide 83

Slide 83 text

@mheap #dpc18 Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I fill in "search" with "Behavior Driven Development" And I press "searchButton" Then I should see "agile software development"

Slide 84

Slide 84 text

@mheap #dpc18 Custom Steps

Slide 85

Slide 85 text

@mheap #dpc18 Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I fill in "search" with "Behavior Driven Development" And I press "searchButton" Then I should see "agile software development"

Slide 86

Slide 86 text

@mheap #dpc18 Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I search for "Behavior Driven Development" Then I should see "agile software development"

Slide 87

Slide 87 text

@mheap #dpc18 Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I search for "Behavior Driven Development" Then I should see "agile software development" 1 scenario (1 undefined) 3 steps (1 passed, 1 undefined, 1 skipped) 0m3.63s (10.62Mb) >> my_suite suite has undefined steps. Please choose the context to generate snippets: [0] None [1] FeatureContext [2] Behat\MinkExtension\Context\MinkContext

Slide 88

Slide 88 text

@mheap #dpc18 [0] None [1] FeatureContext [2] Behat\MinkExtension\Context\MinkContext > 1 --- FeatureContext has missing steps. Define them with these snippets: /** * @When I search for :arg1 */ public function iSearchFor($arg1) { throw new PendingException(); }

Slide 89

Slide 89 text

@mheap #dpc18 ./vendor/bin/behat --append-snippets

Slide 90

Slide 90 text

@mheap #dpc18 [0] None [1] FeatureContext [2] Behat\MinkExtension\Context\MinkContext > 1 u features/bootstrap/FeatureContext.php - `I search for "Behavior Driven Development"` definition added

Slide 91

Slide 91 text

@mheap #dpc18 /** * @When I search for :arg1 */ public function iSearchFor($arg1) { throw new PendingException(); }

Slide 92

Slide 92 text

@mheap #dpc18 /** * @When I search for :term */ public function iSearchFor($term) { throw new PendingException(); }

Slide 93

Slide 93 text

@mheap #dpc18 Feature: Test Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I search for "Behavior Driven Development" TODO: write pending definition Then I should see "agile software development" 1 scenario (1 pending) 3 steps (1 passed, 1 pending, 1 skipped) 0m2.23s (10.61Mb)

Slide 94

Slide 94 text

@mheap #dpc18 /** @BeforeScenario */ public function gatherContexts( Behat\Behat\Hook\Scope\BeforeScenarioScope $scope ) { $environment = $scope ->getEnvironment(); $this ->minkContext = $environment ->getContext( 'Behat\MinkExtension\Context\MinkContext' ); }

Slide 95

Slide 95 text

@mheap #dpc18 /** * Fills in form field with specified id|name|label|value * Example: When I fill in "username" with: "bwayne" * Example: And I fill in "bwayne" for "username" * * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" with "(?P( ?:[^"]|\ ")*)"$/ * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" with:$/ * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" for "(?P( ?:[^"]|\ ")*)"$/ */ public function fillField($field, $value) { $field = $this ->fixStepArgument($field); $value = $this ->fixStepArgument($value); $this ->getSession() ->getPage() ->fillField($field, $value); }

Slide 96

Slide 96 text

@mheap #dpc18 /** * Fills in form field with specified id|name|label|value * Example: When I fill in "username" with: "bwayne" * Example: And I fill in "bwayne" for "username" * * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" with "(?P( ?:[^"]|\ ")*)"$/ * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" with:$/ * @When /^( ?:|I )fill in "(?P( ?:[^"]|\ ")*)" for "(?P( ?:[^"]|\ ")*)"$/ */ public function fillField($field, $value) { $field = $this ->fixStepArgument($field); $value = $this ->fixStepArgument($value); $this ->getSession() ->getPage() ->fillField($field, $value); }

Slide 97

Slide 97 text

@mheap #dpc18 /** * @When I search for :term */ public function iSearchFor($term) { $this ->minkContext ->fillField('search', $term); }

Slide 98

Slide 98 text

@mheap #dpc18 /** * Presses button with specified id|name|title|alt|value * Example: When I press "Log In" * Example: And I press "Log In" * @When /^( ?:|I )press "(?P( ?:[^"]|\ ")*)"$/ */ public function pressButton($button) { $button = $this ->fixStepArgument($button); $this ->getSession() ->getPage() ->pressButton($button); }

Slide 99

Slide 99 text

@mheap #dpc18 /** * Presses button with specified id|name|title|alt|value * Example: When I press "Log In" * Example: And I press "Log In" * @When /^( ?:|I )press "(?P( ?:[^"]|\ ")*)"$/ */ public function pressButton($button) { $button = $this ->fixStepArgument($button); $this ->getSession() ->getPage() ->pressButton($button); }

Slide 100

Slide 100 text

@mheap #dpc18 /** * @When I search for :term */ public function iSearchFor($term) { $this ->minkContext ->fillField('search', $term); $this ->minkContext ->pressButton('searchButton'); }

Slide 101

Slide 101 text

@mheap #dpc18 $ ./vendor/bin/behat Feature: Test Scenario: Searching for a page that exists Given I am on "/wiki/Main_Page" When I search for "Behavior Driven Development" Then I should see "agile software development" 1 scenario (1 passed) 3 steps (3 passed)

Slide 102

Slide 102 text

@mheap #dpc18 Scenario: Successful login When I make a "GET" request to "/?username=michael&key=kangar00s" Then the response status code should be "200" And the "success" property equals "true" And the "user.name" property equals "michael”

Slide 103

Slide 103 text

@mheap #dpc18 Scenario: Successful login When I log in as "Michael" with the password "kangar00s" Then the response status code should be "200" And the "success" property equals "true" And the "user.name" property equals "michael”

Slide 104

Slide 104 text

@mheap #dpc18 Snapshot testing

Slide 105

Slide 105 text

@mheap #dpc18 public function toJson(){ return json_encode(["id" => $this ->id]); } —— public function test_json_formatting_is_correct() { $order = new Order(1); $this ->assertMatchesSnapshot($order ->toJson()); }

Slide 106

Slide 106 text

@mheap #dpc18 $ ./vendor/bin/phpunit There was 1 incomplete test: 1) ExampleTest ::test_json_formatting_is_correct Snapshot created for ExampleTest __test_json_formatting_is_correct

Slide 107

Slide 107 text

@mheap #dpc18 $ ./vendor/bin/phpunit OK (1 test, 1 assertion)

Slide 108

Slide 108 text

@mheap #dpc18 public function toJson(){ return json_encode(["id" => 'Q'.$this ->id]); }

Slide 109

Slide 109 text

@mheap #dpc18 $ ./vendor/bin/phpunit 1) ExampleTest ::test_json_formatting_is_correct Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'{"id": 1}' +'{"id": "Q1"}' FAILURES! Tests: 1, Assertions: 1, Failures: 1.

Slide 110

Slide 110 text

@mheap #dpc18 https: //github.com/spatie/phpunit-snapshot-assertions

Slide 111

Slide 111 text

@mheap #dpc18 Conclusion

Slide 112

Slide 112 text

@mheap #dpc18 Behat

Slide 113

Slide 113 text

@mheap #dpc18 Mountebank

Slide 114

Slide 114 text

@mheap #dpc18 Mink

Slide 115

Slide 115 text

@mheap #dpc18 Snapshot Testing

Slide 116

Slide 116 text

@mheap #dpc18 We don’t have time for this

Slide 117

Slide 117 text

@mheap #dpc18 Where should we start?

Slide 118

Slide 118 text

@mheap #dpc18 Start refactoring

Slide 119

Slide 119 text

@mheap #dpc18 Characterisation tests aren’t perfect

Slide 120

Slide 120 text

@mheap #dpc18 The 4 stages of characterisation tests

Slide 121

Slide 121 text

@mheap #dpc18 Examine your feature

Slide 122

Slide 122 text

@mheap #dpc18 Characterize your feature

Slide 123

Slide 123 text

@mheap #dpc18 Refactor your feature

Slide 124

Slide 124 text

@mheap #dpc18 Delete your tests

Slide 125

Slide 125 text

@mheap #dpc18 for characterization tests

Slide 126

Slide 126 text

@mheap #dpc18 I’m Michael

Slide 127

Slide 127 text

@mheap #dpc18 @mheap

Slide 128

Slide 128 text

@mheap #dpc18 https://joind.in/talk/b7df0

Slide 129

Slide 129 text

@mheap #dpc18 Michael @mheap https://joind.in/talk/b7df0 Thankyou!