@asgrim Crafting Quality PHP Applications James Titcumb PHP Benelux 2018
$ whoami James Titcumb www.jamestitcumb.com www.roave.com @asgrim
@asgrim
@asgrim What is “quality”?
@asgrim photo: Rob Allen https://flic.kr/p/qmGpsq
@asgrim photo: https://goo.gl/QHWXQL
@asgrim photo: 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 Products Long-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 it for 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 AssertAssertion; // 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 <?php function 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 <?php function 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 <?php function 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 <?php function 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 <?php function 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 <?php function 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 FooBar */ 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 UI public 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 tests public 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

Crafting Quality PHP Applications (PHP Benelux 2018)