Upload
james-titcumb
View
31
Download
1
Embed Size (px)
Citation preview
@asgrim
Crafting Quality PHP Applications: an overview
James TitcumbPHPSW 2018 meetup
$ 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
Testing should be an assumption
@asgrim
You’re already testing.
@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/96cee
James Titcumb@asgrim