crafting quality php applications (php benelux 2018)

107
@asgrim Crafting Quality PHP Applications James Titcumb PHP Benelux 2018

Upload: james-titcumb

Post on 29-Jan-2018

106 views

Category:

Technology


0 download

TRANSCRIPT

@asgrim

Crafting Quality PHP Applications

James TitcumbPHP Benelux 2018

$ whoami

James Titcumb

www.jamestitcumb.com

www.roave.com

@asgrim

@asgrim

@asgrim

What is “quality”?

@asgrimphoto: Rob Allen https://flic.kr/p/qmGpsq

@asgrimphoto: https://goo.gl/QHWXQL

@asgrimphoto: Rob Allen https://flic.kr/p/ecFbH8

@asgrim

Quality.

@asgrim

"The best code is no code at all”-- Jeff Atwood

source: https://blog.codinghorror.com/the-best-code-is-no-code-at-all/

@asgrim

Trade-offs.

@asgrim

Prototyping & short-lived apps/sites

@asgrim

ProductsLong-lived projects. Open source software.

@asgrim

What is quality in applications?

@asgrim

What about time/cost…?

@asgrim

“A freelancer at $25 an hour for 100 hours still costs more than a freelancer at $150 an hour that

takes 10 hours to do the same task”-- Brandon Savage

source: http://www.brandonsavage.net/earning-more-money-as-a-php-freelancer/

@asgrim

Get an expert in.

@asgrim

Complexity

@asgrim

Processes

@asgrim

This talk...

● Planning● Development● Testing● Continuous integration● Code reviews● Deployments

@asgrim

Planning

@asgrim

Planning is communication

@asgrim

Use business terminology

@asgrim

Explore and discover

@asgrim

Build a model of the business

@asgrim

@asgrim

@asgrim

The model must be fluid.

@asgrim

Development

@asgrim

Care about code

@asgrim

“There are only two hard things in Computer Science: cache invalidation and naming things.”

-- Phil Karlton

source: https://martinfowler.com/bliki/TwoHardThings.html

@asgrim

SimpleBeanFactoryAwareAspectInstanceFactory

@asgrim

Loader

@asgrim

Describe intent

@asgrim

“Give awkward names to awkward concepts”-- Eric Evans

source: https://skillsmatter.com/conferences/8231-ddd-exchange-2017

@asgrim

SOLID

@asgrim

KISS

@asgrim

Object Calisthenics

@asgrim

Avoid early abstraction

@asgrim

“Code for your use-case,not for your re-use-case”

-- Marco Pivetta

source: https://ocramius.github.io/extremely-defensive-php/#/39

@asgrim

Care about your API

@asgrim

A public method is like a child:once you've written it,

you are going to maintain itfor the rest of its life!

-- Stefan Priebsch

@asgrim

Strict type declarations.Use declare(strict_types=1); by default

@asgrim

Immutable value objects

declare(strict_types=1);use Assert\Assertion; // beberlei/assert library !final class PostalCode{ private const VALIDATION_EXPRESSION = '(GIR 0AA)|((([A-Z-[QVX]][0-9][0-... /** @var string */ private $value; public function __construct(string $value) { $this->assertValidPostalCode($value); $this->value = $value; } private function assertValidPostalCode(string $value) : string { Assertion::regex($value, self::VALIDATION_EXPRESSION); } public function __toString() : string { return $this->value; }}

@asgrim

Value objects are valid!

declare(strict_types=1);

final class Thing { public function assignPostalCode(PostalCode $postalCode) : void { // ... we can trust $postalCode is a valid postal code }}

// 12345 is not valid - EXCEPTION!$myThing->assignPostalCode(new PostalCode('12345'));

// assignPostalCode is happy$myThing->assignPostalCode(new PostalCode('PO1 1AA'));

// With STRICT types, this will also FAIL$myThing->assignPostalCode(new PostalCode(12345));

@asgrim

Testing

@asgrim

Testing is NOT a separate line item

@asgrim

Still need convincing?

@asgrim

How?

@asgrim

Reduce complexity

@asgrim

Reduce complexity

public function process(Stuff a, string b, int c) : MoreStuff{ // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code // lots of complicated code

@asgrim

Reduce complexity

public function meaningfulThing(Stuff a, string b, int c) : More{ // call nicer, meaningful methods below}

private function throwStuffIntoFire(Stuff a) : Fire{ // smaller, meaningful chunk of code}

private function combineStringWithFire(Fire a, string b) : string{ // simple, lovely code!}

private function postFlowersToSpain(int c) : void

@asgrim

More complexity = more tests

@asgrim

Test coverage

@asgrim

Line coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);

@asgrim

Line coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);

@asgrim

Branch coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);

@asgrim

Branch coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);

@asgrim

Branch coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);foo(false, false); // NEW TEST!!!

@asgrim

Branch coverage

<?phpfunction foo(bool $a, bool $b) { if ($a) { echo "A"; } if ($b) { echo "B"; }}

// generate coverage using calls:foo(true, false);foo(false, true);foo(false, false); // NEW TEST!!!foo(true, true); // NEW TEST!!!

@asgrim

Prevent coverage leaking

@asgrim

Prevent coverage leaking

<?php

namespace Foo;

/** * @covers \Foo\Bar */final class BarTest extends TestCase{ // write some tests!}

@asgrim

Are the tests testing?

@asgrim

Example of a test not testing…

public function testPurchaseTickets(){ $event = Event::create(uniq('event', true) $customer = Customer::create(uniq('name', true)); $shop->purchaseTicket(1, $event, [$customer]);}

@asgrim

@asgrim

Assert you’re asserting

public function testPurchaseTickets(){ $event = Event::create(uniq('event', true) $customer = Customer::create(uniq('name', true)); $receipt = $shop->purchaseTicket(1, $event, [$customer]);

self::assertSame($event, $receipt->event()); $customersInReceipt = $receipt->customers(); self::assertCount(1, $customersInReceipt); self::assertContains($customer, $customersInReceipt);}

@asgrim

@asgrim

Test the tests are testing!

@asgrim

Mutation testing

function add(int $a, int $b) : int {

return $a + $b;

}

function testAdd() {

$result = add(2, 3);

// self::assertSame(5, $result);

}

@asgrim

Mutation testing

function add(int $a, int $b) : int {

return $a - $b;

}

function testAdd() {

$result = add(2, 3);

// self::assertSame(5, $result);

}

@asgrim

Mutation testing

function add(int $a, int $b) : int {

return $a - $b;

}

function testAdd() {

$result = add(2, 3);

self::assertSame(5, $result);

// /\/\/\ test will now fail with mutation

}

@asgrim

What about other tests?

@asgrim

Integration tests

@asgrim

Behaviour tests

@asgrim

BAD! Do not do this.

Feature: Ability to print my boarding pass

Scenario: A checked in passenger can print their boarding pass Given I have a flight booked And I have checked in When I visit the home page And I click the ".manage-booking" button And I enter "CJM23L" in the ".bref-ipt-fld" field And I click the "Find Booking" button Then I should see the ".booking-ref.print" button When I click the ".booking-ref.print" button Then I should see the print dialogue

@asgrim

Better Behaviour test

Feature: Ability to print my boarding pass

Policies: - Boarding passes are only available when already checked in - If customer cannot print boarding pass, they can collect at The airport - more business rules etc...

Scenario: A checked in passenger can print their boarding pass Given I have a flight booked for LHR-OTP on 24th May And I have previously checked in for the flight When I display my booking reference "CJM23L" Then I should be able to print my boarding pass And the boarding pass should display flight details correctly

@asgrim

Why is this important?

@asgrim

Automate these tests

@asgrim

Automated tests

Application (and UI, API, etc.)

Domain / Business Logic

Infrastructure (DBAL, APIs, etc.)

@asgrim

Automated tests

Application (and UI, API, etc.)

Domain / Business Logic

Infrastructure (DBAL, APIs, etc.)

@asgrim

Testing at domain layer

Application (UI, API, etc.)

Domain / Business Logic

Infrastructure (DB, APIs, etc.)

// Testing via the UIpublic function iDisplayMyBookingReference(string $reference){ $page = $this->getSession()->getPage(); $page->click(".manage-booking"); $page->findField(".bref-ipt-fld")->setValue($reference); $page->click("Find Booking");}

// Using the domain layer directly in testspublic function iDisplayMyBookingReference(string $reference){ $this->booking = $this->retrieveBooking($reference);}

@asgrim

Some UI testing is okay!!!

Feature: Ability to print my boarding pass

Policies: - Boarding passes are only available when already checked in - If customer cannot print boarding pass, they can collect at The airport - more business rules etc...

@ui Scenario: A checked in passenger can print their boarding pass Given I have a flight booked for LHR-OTP on 24th May And I have previously checked in for the flight When I display my booking reference "CJM23L" Then I should be able to print my boarding pass And the boarding pass should display flight details correctly

@asgrim

Automate all the things!

@asgrim

Continuous Integration

@asgrim

Tests cost money to run(manually)

@asgrim

Travis-CI.com

@asgrim

Jenkins

@asgrim

What to automate?

@asgrim

The goal?

@asgrim

Code reviews

@asgrim

What to look for in code review?

@asgrim

What to look for in code review?Code style.

@asgrim

What to look for in code review?Small, atomic changes.

@asgrim

What to look for in code review?Composer versions.

@asgrim

What to look for in code review?Structure & good practices.

@asgrim

What to look for in code review?Tests.

@asgrim

What to look for in code review?Documentation.

@asgrim

What to look for in code review?Security.

@asgrim

What to look for in code review?Insight.

@asgrim

It takes practice.

@asgrim

Deployments

@asgrim

Automate deployments!

@asgrim

One-click deployments

@asgrim

“Move fast and break things”-- a stupid Facebook mantra

@asgrim

“Move fast and break things with stable infra”-- Facebook mantra since 2014

@asgrim

Continuous Delivery & Deployment

@asgrim

Better quality

@asgrim

Better quality = Higher confidence

@asgrim

Better quality = Higher confidence = Happy customers

Any questions?

https://joind.in/talk/f1370

James Titcumb@asgrim