Slide 1

Slide 1 text

Diego Aguiar Beltrán @MolloKhan

Slide 2

Slide 2 text

❖ Developer at SymfonyCasts ❖ Hiking ❖ Gaming (Warcraft FTW!) ❖Tepic, Mexico @MolloKhan

Slide 3

Slide 3 text

Topics Overview London Style Chicago Style Bank Kata Demonstration Design Comparison

Slide 4

Slide 4 text

❖ ❖ ❖

Slide 5

Slide 5 text

UI API Domain Persistence Development Workflow London Style User Service User Repository User API Register User Create User Store User Next Feature

Slide 6

Slide 6 text

❖ ❖ ❖ ❖ ❖ ❖

Slide 7

Slide 7 text

❖ ❖ ❖

Slide 8

Slide 8 text

UI API Domain Persistence User Service User Repository User API Chicago Style Register User Create User Store User Next Feature

Slide 9

Slide 9 text

❖ ❖ ❖ ❖ ❖ ❖

Slide 10

Slide 10 text

Bank Kata Create a simple bank application with the following features: ❖Deposit into Account ❖Withdraw from an Account ❖Print a bank statement to the console

Slide 11

Slide 11 text

Starting point and constraints: 1. Start with a class with the following structure (cannot add any other public methods) 2. Use Strings and Integers for dates and amounts 3. Don’t worry about spacing in the bank statement public class Account { public function deposit(int $amount): void public function withdraw(int $amount): void public function printStatement(): void }

Slide 12

Slide 12 text

Bank Statement Example Date | Amount| Balance 07/12/2023 | -500 | 1500 06/12/2023 | 1000 | 2000 05/12/2023 | 1000 | 1000

Slide 13

Slide 13 text

Where do we start? ❖ Write an Acceptance Test ❖ Take Design Decisions ❖ Unit test the App ❖ Tests will guide the app’s architecture London Style Bank Kata

Slide 14

Slide 14 text

class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { // Arrange $account = new Account(); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } } London Style Bank Kata

Slide 15

Slide 15 text

public function testPrintStatementWithAllTransactions() { // Assert $console = $this->createMock(Console::class); $console ->expects(self::exactly(4)) ->method('printLine') ->withConsecutive( ['DATE | AMOUNT | BALANCE'], ['07/12/2023 | -100.00 | 1400.00'], ['06/12/2023 | 500.00 | 1500.00'], ['05/12/2023 | 1000.00 | 1000.00'] ); } London Style Bank Kata

Slide 16

Slide 16 text

class Account { public function deposit(int $amount): void { } public function withdraw(int $amount): void { } public function printStatement(): void { } } class Console { public function printLine(string $line): void { throw new \RuntimeException('TODO'); } } London Style Bank Kata

Slide 17

Slide 17 text

London Style Bank Kata

Slide 18

Slide 18 text

class AccountTest extends TestCase { public function testDeposit_should_store_transaction() { $account = new Account(); $account->deposit(1000); } } London Style Bank Kata

Slide 19

Slide 19 text

class AccountTest extends TestCase { public function testDeposit_should_store_transaction() { $repository = $this->createMock(TransactionRepository::class); $repository->expects(self::once()) ->method('storeDeposit') ->with(1000); $account = new Account($repository); $account->deposit(1000); } } London Style Bank Kata

Slide 20

Slide 20 text

class TransactionRepository { public function storeDeposit(int $amount) { throw new \RuntimeException('TODO’); } } class Account { public function __construct( private TransactionRepository $repository ) { } } London Style Bank Kata

Slide 21

Slide 21 text

class TransactionRepository { public function storeDeposit(int $amount) { throw new \RuntimeException('TODO’); } } class Account { public function __construct( private TransactionRepository $repository ) { } } London Style Bank Kata

Slide 22

Slide 22 text

London Style Bank Kata class Account { public function deposit(int $amount): void { $this->transactionRepository->storeDeposit($amount); } }

Slide 23

Slide 23 text

London Style Bank Kata class Account { public function deposit(int $amount): void { $this->transactionRepository->storeDeposit($amount); } }

Slide 24

Slide 24 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { $repository = new TransactionRepository(); $account = new Account($repository); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 25

Slide 25 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { $repository = new TransactionRepository(); $account = new Account($repository); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 26

Slide 26 text

London Style Bank Kata class TransactionRepositoryTest extends TestCase { public function testStoreDeposit_create_and_store_transaction() { $repository = new TransactionRepository(); $repository->storeDeposit(1000); // $transaction = ?? self::assertSame(1000, $transaction->getAmount()); self::assertSame(‘07/12/2023', $transaction->getDate()); } }

Slide 27

Slide 27 text

London Style Bank Kata class TransactionRepositoryTest extends TestCase { public function testStoreDeposit_create_and_store_transaction() { $repository = new TransactionRepository(); $repository->storeDeposit(1000); $transactions = $repository->fetchAll(); self::assertCount(1, $transactions); self::assertSame(1000, $transactions[0]->getAmount()); self::assertSame(‘07/12/2023', $transactions[0]->getDate()); } }

Slide 28

Slide 28 text

London Style Bank Kata class TransactionRepository { private array $transactions = []; public function storeDeposit(int $amount): void { $date = (new \DateTime())->format('d/m/Y'); $this->transactions[] = new Transaction($amount, $date); } public function fetchAll(): array { return $this->transactions; } }

Slide 29

Slide 29 text

London Style Bank Kata class Transaction { public function __construct( private readonly int $amount, private readonly string $date ) { } // getters … }

Slide 30

Slide 30 text

London Style Bank Kata class TransactionRepository { private array $transactions = []; public function storeDeposit(int $amount): void { $date = (new \DateTime())->format('d/m/Y'); $this->transactions[] = new Transaction($amount, $date); } public function fetchAll(): array { return $this->transactions; } }

Slide 31

Slide 31 text

London Style Bank Kata class TransactionRepositoryTest extends TestCase { public function testStoreDeposit_create_and_store_transaction() { $clock = new MockClock('2023-12-07'); $repository = new TransactionRepository($clock); … } }

Slide 32

Slide 32 text

London Style Bank Kata class TransactionRepository { public function __construct(private ClockInterface $clock) { } public function storeDeposit(int $amount): void { $date = $this->clock->now()->format('d/m/Y’)); $this->transactions[] = new Transaction($amount, $date); } }

Slide 33

Slide 33 text

London Style Bank Kata class TransactionRepository { public function __construct(private ClockInterface $clock) { } public function storeDeposit(int $amount): void { $date = $this->clock->now()->format('d/m/Y’)); $this->transactions[] = new Transaction($amount, $date); } }

Slide 34

Slide 34 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { … $clock = new MockClock('2023-12-05'); $repository = new TransactionRepository($clock); $account = new Account($repository); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 35

Slide 35 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { … $clock = new MockClock('2023-12-05'); $repository = new TransactionRepository($clock); $account = new Account($repository); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 36

Slide 36 text

London Style Bank Kata class AccountTest extends TestCase { public function testWithdraw_should_store_transaction() { $this->repository->expects(self::once()) ->method('storeWithdrawal') ->with(1000); $this->account->withdraw(1000); } }

Slide 37

Slide 37 text

London Style Bank Kata class Account { public function withdraw(int $amount) { $this->transactionRepository ->storeWithdrawal($amount); } … } class TransactionRepository { public function storeWithdrawal(int $amount) { throw new \RuntimeException('TODO'); } … }

Slide 38

Slide 38 text

London Style Bank Kata class Account { public function withdraw(int $amount) { $this->transactionRepository ->storeWithdrawal($amount); } … } class TransactionRepository { public function storeWithdrawal(int $amount) { throw new \RuntimeException('TODO'); } … }

Slide 39

Slide 39 text

London Style Bank Kata class TransactionRepositoryTest extends TestCase { public function testStoreWithdrawal_create_and_store_transaction() { $this->repository->storeWithdrawal(1000); $transactions = $this->repository->fetchAll(); self::assertCount(1, $transactions); self::assertSame('07/12/2023', $transactions[0]->getDate()); self::assertSame(-1000, $transactions[0]->getAmount()); } }

Slide 40

Slide 40 text

London Style Bank Kata class TransactionRepositoryTest extends TestCase { public function testStoreWithdrawal_create_and_store_transaction() { $this->repository->storeWithdrawal(1000); $transactions = $this->repository->fetchAll(); self::assertCount(1, $transactions); $transaction = $transactions[0]; self::assertSame(-1000, $transaction->getAmount()); self::assertSame('07/12/2023', $transaction->getDate()); } }

Slide 41

Slide 41 text

London Style Bank Kata class TransactionRepository { public function storeWithdrawal(int $amount): void { $date = $this->clock->now()->format('d/m/Y')) $transaction = new Transaction(-$amount, $date); $this->transactions[] = $transaction; } }

Slide 42

Slide 42 text

London Style Bank Kata class TransactionRepository { public function storeWithdrawal(int $amount): void { $date = $this->clock->now(); $transaction = new Transaction(-$amount, $date->format('d/m/Y')); $this->transactions[] = $transaction; } }

Slide 43

Slide 43 text

London Style Bank Kata

Slide 44

Slide 44 text

London Style Bank Kata class AccountTest extends TestCase { public function testPrintStatement() { $transactions = [new Transaction(1000, '07/12/2023’)]; $this->repository->expects(self::once()) ->method('fetchAll') ->willReturn($transactions); $this->statementPrinter->expects(self::once()) ->method('print') ->with($transactions); $this->account->printStatement(); } }

Slide 45

Slide 45 text

London Style Bank Kata class Account { public function __construct( private TransactionRepository $transactionRepository, private StatementPrinter $statementPrinter, ) { } public function printStatement(): void { $transactions = $this->transactionRepository->fetchAll(); $this->statementPrinter->print($transactions); } }

Slide 46

Slide 46 text

London Style Bank Kata class Account { public function __construct( private TransactionRepository $transactionRepository, private StatementPrinter $statementPrinter, ) { } public function printStatement(): void { $transactions = $this->transactionRepository->fetchAll(); $this->statementPrinter->print($transactions); } }

Slide 47

Slide 47 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { $clock = new MockClock('2023-05-12'); $repository = new TransactionRepository($clock); $statementPrinter = new StatementPrinter(); $account = new Account($repository, $statementPrinter); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 48

Slide 48 text

London Style Bank Kata class AccountFeatureTest extends TestCase { public function testPrintStatementWithAllTransactions() { $clock = new MockClock('2023-05-12'); $repository = new TransactionRepository($clock); $statementPrinter = new StatementPrinter(); $account = new Account($repository, $statementPrinter); $account->deposit(1000); $account->deposit(500); $account->withdraw(100); $account->printStatement(); } }

Slide 49

Slide 49 text

London Style Bank Kata class StatementPrinterTest extends TestCase { public function testPrint_transactions_in_descending_order() { $this->console->expects(self::exactly(3)) ->method('printLine') ->withConsecutive( ['DATE | AMOUNT | BALANCE'], ['07/12/2023 | -100 | 900'], ['06/12/2023 | 1000 | 1000'], ); $this->statementPrinter->print([ new Transaction(1000, '06/12/2023'), new Transaction(-100, '07/12/2023'), ]); } }

Slide 50

Slide 50 text

class StatementPrinter { public function __construct( private Console $console ) { } public function print(array $transactions) { } } class Console { public function printLine(string $line) { echo $line . PHP_EOL; } } London Style Bank Kata

Slide 51

Slide 51 text

class StatementPrinter { public function __construct( private Console $console ) { } public function print(array $transactions) { } } class Console { public function printLine(string $line) { echo $line . PHP_EOL; } } London Style Bank Kata

Slide 52

Slide 52 text

London Style Bank Kata class StatementPrinter { public function print(array $transactions): void { $this->console->printLine('DATE | AMOUNT | BALANCE'); $statementLines = $this->getStatementLines($transactions); array_map($this->console->printLine(...), array_reverse($statementLines)); } }

Slide 53

Slide 53 text

London Style Bank Kata private function getStatementLines(array $transactions): array { $statementLines = []; $runningBalance = 0; foreach ($transactions as $transaction) { $runningBalance += $transaction->getAmount(); $statementLines[] = sprintf('%s | %f | %f', $transaction->getDate(), $transaction->getAmount(), $runningBalance ); } return $statementLines; }

Slide 54

Slide 54 text

London Style Bank Kata private function getStatementLines(array $transactions): array { $statementLines = []; $runningBalance = 0; foreach ($transactions as $transaction) { $runningBalance += $transaction->getAmount(); $statementLines[] = sprintf('%s | %f | %f', $transaction->getDate(), $transaction->getAmount(), $runningBalance ); } return $statementLines; }

Slide 55

Slide 55 text

London Style Bank Kata

Slide 56

Slide 56 text

London Style Bank Kata Utility API Domain Persistence Entities Account Statement Printer Transaction Repository Transaction Console

Slide 57

Slide 57 text

Chicago Style Bank Kata Account Statement Printer Console API Domain Persistence Entities Transaction Service Transaction Repository Transaction + deposit() + withdraw() + getTransactions() + store() + fetchAll() - Amount - Date - Balance - Operation Utility

Slide 58

Slide 58 text

❖ ❖ ❖ ❖

Slide 59

Slide 59 text

@MolloKhan