Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD Styles: London VS Chicago

Diego Aguiar
December 09, 2023

TDD Styles: London VS Chicago

Did you know that there are different TDD styles? The most famous ones are London and Chicago styles

The London style comes from the mockist school and it's an "outside-in" approach. It starts from the outside of the application (APIs/Controllers) and works its way down to the domain layer.

The Chicago style comes from the classicist school and it's an "inside-out" approach. It starts from the domain layer and and works out towards the APIs/Controllers.

In this talk we'll discover what make those styles unique, what are their key differences, and which are their advantages and disadvantages

Diego Aguiar

December 09, 2023
Tweet

More Decks by Diego Aguiar

Other Decks in Programming

Transcript

  1. UI API Domain Persistence Development Workflow London Style User Service

    User Repository User API Register User Create User Store User Next Feature
  2. UI API Domain Persistence User Service User Repository User API

    Chicago Style Register User Create User Store User Next Feature
  3. 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
  4. 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 }
  5. Bank Statement Example Date | Amount| Balance 07/12/2023 | -500

    | 1500 06/12/2023 | 1000 | 2000 05/12/2023 | 1000 | 1000
  6. 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
  7. 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
  8. 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
  9. 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
  10. class AccountTest extends TestCase { public function testDeposit_should_store_transaction() { $account

    = new Account(); $account->deposit(1000); } } London Style Bank Kata
  11. 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
  12. class TransactionRepository { public function storeDeposit(int $amount) { throw new

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

    \RuntimeException('TODO’); } } class Account { public function __construct( private TransactionRepository $repository ) { } } London Style Bank Kata
  14. London Style Bank Kata class Account { public function deposit(int

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

    $amount): void { $this->transactionRepository->storeDeposit($amount); } }
  16. 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(); } }
  17. 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(); } }
  18. 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()); } }
  19. 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()); } }
  20. 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; } }
  21. London Style Bank Kata class Transaction { public function __construct(

    private readonly int $amount, private readonly string $date ) { } // getters … }
  22. 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; } }
  23. 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); … } }
  24. 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); } }
  25. 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); } }
  26. 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(); } }
  27. 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(); } }
  28. 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); } }
  29. 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'); } … }
  30. 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'); } … }
  31. 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()); } }
  32. 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()); } }
  33. 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; } }
  34. 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; } }
  35. 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(); } }
  36. 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); } }
  37. 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); } }
  38. 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(); } }
  39. 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(); } }
  40. 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'), ]); } }
  41. 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
  42. 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
  43. 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)); } }
  44. 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; }
  45. 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; }
  46. London Style Bank Kata Utility API Domain Persistence Entities Account

    Statement Printer Transaction Repository Transaction Console
  47. 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