Slide 1

Slide 1 text

Hexagonal BDD Scato Eggen @scataco

Slide 2

Slide 2 text

Hexagonal BDD Let’s get started!

Slide 3

Slide 3 text

Setup Instructions Clone https://github.com/scato/hexagonal-bdd.git and follow the instructions in README.md

Slide 4

Slide 4 text

Overview ● User Stories ● Head First: Behat ● Hexagonal Architecture ● Head First: PHPSpec ● Test Driven Development ● Test Doubles ● Behaviour Driven Development ● Test Terminology

Slide 5

Slide 5 text

User Stories

Slide 6

Slide 6 text

User Stories Feature: Start a new game In order to practice my tic-tac-toe skills As a player I need to be able to start a new game Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" Then I should see an empty board

Slide 7

Slide 7 text

User Stories “Software requirements is a communication problem” – Mike Cohn As a … I want … So that … role behavior business value

Slide 8

Slide 8 text

User Stories: Example As a player I want to start a new game of tic-tac-toe So that I can practice my tic-tac-toe skills

Slide 9

Slide 9 text

User Stories: Roles As a player ● Identify the roles used to interact with the system ● Look at features from the user’s point of view

Slide 10

Slide 10 text

User Stories: Behavior I want to start a new game of tic-tac-toe ● Focus on the intent of the user ● Determine conditions of satisfaction

Slide 11

Slide 11 text

User Stories: Business Value So that I can practice my tic-tac-toe skills ● Provide background for making informed guesses ● Focus on adding value

Slide 12

Slide 12 text

User Stories: Conditions of Satisfaction ● Verify that a game has been started. ● Verify that the board is empty at the start of the game. ● Also known as: acceptance criteria

Slide 13

Slide 13 text

Behaviour Driven Design “Acceptance criteria should be executable” – Dan North Given some initial context (the givens) When an event occurs Then ensure some outcomes

Slide 14

Slide 14 text

Head First: Behat

Slide 15

Slide 15 text

Head First: Behat

Slide 16

Slide 16 text

Behat: Getting Started $ vendor/bin/behat --init # features/start-game.feature Feature: Start a new game In order to practice my tic-tac-toe skills As a player I need to be able to start a new game

Slide 17

Slide 17 text

Behat: Scenarios # features/start-game.feature Feature: Start a new game In order to practice my tic-tac-toe skills As a player I need to be able to start a new game Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" Then I should see an empty board

Slide 18

Slide 18 text

Behat: Step Definitions class FeatureContext implements Context, SnippetAcceptingContext { // ... } $ vendor/bin/behat --append-snippets

Slide 19

Slide 19 text

Behat: Step Definitions Scenario: Choose to go first Given I have not started a game yet TODO: write pending definition When I start a game as player "X" Then I should see an empty board 1 scenario (1 pending) 3 steps (1 pending, 2 skipped) 0m0.03s (10.87Mb)

Slide 20

Slide 20 text

Behat: Step Definitions /** * @Given I have not started a game yet */ public function iHaveNotStartedAGameYet() { throw new PendingException(); }

Slide 21

Slide 21 text

Behat: Step Definitions /** * @Given I have not started a game yet */ public function iHaveNotStartedAGameYet() { $this->gameRepository = new FakeGameRepository([]); }

Slide 22

Slide 22 text

Behat: Fakes // features/bootstrap/Fake/FakeGameRepository.php namespace Fake; class FakeGameRepository { private $games; public function __construct(array $games) { $this->games = $games; } }

Slide 23

Slide 23 text

Behat: Step Definitions Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" TODO: write pending definition Then I should see an empty board 1 scenario (1 pending) 3 steps (1 passed, 1 pending, 1 skipped) 0m0.03s (10.87Mb)

Slide 24

Slide 24 text

Behat: Step Definitions /** * @When I start a game as player :arg1 */ public function iStartAGameAsPlayer($arg1) { throw new PendingException(); }

Slide 25

Slide 25 text

Hexagonal Architecture

Slide 26

Slide 26 text

Hexagonal Architecture “Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.” – Alistair Cockburn ● UI tests are slow and brittle. ● Business logic always creeps into the user interface code. ● Also known as “ports & adapters”.

Slide 27

Slide 27 text

Hexagonal Architecture up and down don’t mean anything

Slide 28

Slide 28 text

Hexagonal Architecture getting rid of up and down

Slide 29

Slide 29 text

Hexagonal Architecture inside and outside actually mean something

Slide 30

Slide 30 text

Growing Object-Oriented Software “Acceptance tests are customer-facing tests that capture the domain logic the system must perform and demonstrates that it performs them.” – Nat Pryce ● Use Ports & Adapters to run acceptance-tests directly against the application domain model. ● The domain model is cleanly decoupled from the technical infrastructure that connects it to the outside world.

Slide 31

Slide 31 text

Growing Object-Oriented Software

Slide 32

Slide 32 text

“A project needs a common language that is more robust than the lowest common denominator.” – Eric Evans ● Domain experts should object to terms or structures that are awkward or inadequate to convey domain understanding ● Developers should watch for ambiguity or inconsistency that will trip up design. Ubiquitous Language

Slide 33

Slide 33 text

Modelling by Example “By embedding Ubiquitous Language in your scenarios, your scenarios naturally become your domain model, which you can use to develop the most important part of your application - a core domain.” – Konstantin Kudryashov ● Push for Ubiquitous Language in your scenarios. ● Do Domain-Driven Design while you're doing the red- green-refactor cycle.

Slide 34

Slide 34 text

Hexagonal Architecture 1. The application core is where the business value is at. 2. The user interface and persistence should be an afterthought. 3. If it’s easy to test, it’s probably hexagonal. 4. Don’t test for things that are not in the Ubiquitous Language.

Slide 35

Slide 35 text

Head First: PHPSpec

Slide 36

Slide 36 text

Head First: PHPSpec

Slide 37

Slide 37 text

PHPSpec: red-green-refactor Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" TODO: write pending definition Then I should see an empty board 1 scenario (1 pending) 3 steps (1 passed, 1 pending, 1 skipped) 0m0.03s (10.87Mb)

Slide 38

Slide 38 text

PHPSpec: red-green-refactor /** * @When I start a game as player :arg1 */ public function iStartAGameAsPlayer($arg1) { throw new PendingException(); }

Slide 39

Slide 39 text

PHPSpec: red-green-refactor /** * @When I start a game as player :arg1 */ public function iStartAGameAsPlayer($arg1) { $command = new StartGameCommand(); $command->gameId = self::GAME_ID; $command->playerName = $arg1; $handler = new StartGameHandler(); $handler->handle($command); }

Slide 40

Slide 40 text

PHPSpec: red-green-refactor $ vendor/bin/phpspec run 1 specs 1 example (1 broken) 18ms Do you want me to create `StartGameCommand` for you? [Y/n] $ vendor/bin/phpspec desc Application/StartGameCommand

Slide 41

Slide 41 text

PHPSpec: red-green-refactor class StartGameCommandSpec extends ObjectBehavior { function it_should_have_a_game_id() { $this->gameId->shouldBeNull(); } }

Slide 42

Slide 42 text

PHPSpec: red-green-refactor class StartGameCommand { /** * @var string */ public $gameId; }

Slide 43

Slide 43 text

PHPSpec: red-green-refactor class StartGameCommandSpec extends ObjectBehavior { function it_should_have_a_player_name() { $this->playerName->shouldBeNull(); } }

Slide 44

Slide 44 text

PHPSpec: red-green-refactor class StartGameCommand { /** * @var string */ public $playerName; }

Slide 45

Slide 45 text

PHPSpec: red-green-refactor $ vendor/bin/phpspec desc Application/StartGameHandler

Slide 46

Slide 46 text

PHPSpec: red-green-refactor class StartGameHandlerSpec extends ObjectBehavior { function it_should_start_a_game() { $command = new StartGameCommand(); $this->handle($command); } }

Slide 47

Slide 47 text

PHPSpec: red-green-refactor /** * @When I start a game as player :arg1 */ public function iStartAGameAsPlayer($arg1) { $command = new StartGameCommand(); $command->gameId = self::GAME_ID; $command->playerName = $arg1; $handler = new StartGameHandler(); $handler->handle($command); }

Slide 48

Slide 48 text

PHPSpec: red-green-refactor /** * @When I start a game as player :playerName */ public function iStartAGameAsPlayer($playerName) { $command = new StartGameCommand(); $command->gameId = self::GAME_ID; $command->playerName = $playerName; $handler = new StartGameHandler(); $handler->handle($command); }

Slide 49

Slide 49 text

PHPSpec: red-green-refactor Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" Then I should see an empty board TODO: write pending definition 1 scenario (1 pending) 3 steps (2 passed, 1 pending) 0m0.03s (10.87Mb)

Slide 50

Slide 50 text

PHPSpec: red-green-refactor /** * @Then I should see an empty board */ public function iShouldSeeAnEmptyBoard() { $query = new GetGameQuery(); $query->gameId = self::GAME_ID; $handler = new GetGameHandler(); $result = $handler->handle($query); PHPUnit_Framework_Assert::assertTrue($result->getBoard()->isEmpty()); }

Slide 51

Slide 51 text

PHPSpec: red-green-refactor Application\GetGameQuery ● it should have a game id Application\GetGameHandler ● it should get a game

Slide 52

Slide 52 text

PHPSpec: red-green-refactor class GetGameHandlerSpec extends ObjectBehavior { function it_should_get_a_game() { } }

Slide 53

Slide 53 text

PHPSpec: red-green-refactor Application\Game\Board ● it should start out empty

Slide 54

Slide 54 text

PHPSpec: red-green-refactor class Board { public function isEmpty() { return true; } }

Slide 55

Slide 55 text

PHPSpec: red-green-refactor Application\Game\Game ● it should have an id ● it should have a board Application\Game\GameRepository ● get(UuidInterface $id): Game

Slide 56

Slide 56 text

PHPSpec: red-green-refactor class GameSpec extends ObjectBehavior { function let(UuidInterface $id) { $this->beConstructedWith($id); } function it_should_have_an_id(UuidInterface $id) { $this->getId()->shouldBe($id); } }

Slide 57

Slide 57 text

PHPSpec: red-green-refactor interface GameRepository { /** * @param UuidInterface $id * @return Game * @throws RuntimeException if game was not found */ public function get(UuidInterface $id); }

Slide 58

Slide 58 text

PHPSpec: red-green-refactor Application\GetGameHandler ● it should get a game

Slide 59

Slide 59 text

PHPSpec: red-green-refactor class GetGameHandlerSpec extends ObjectBehavior { function let( GameRepository $gameRepository, UuidFactoryInterface $uuidFactory, UuidInterface $gameId, Game $game ) { $this->beConstructedWith($gameRepository, $uuidFactory); $uuidFactory->fromString('1234')->willReturn($gameId); $gameRepository->get($gameId)->willReturn($game); } }

Slide 60

Slide 60 text

PHPSpec: red-green-refactor class GameRepositoryStub implements GameRepository { public function get(UuidInterface $id) { // TODO: Implement get() method. } }

Slide 61

Slide 61 text

PHPSpec: red-green-refactor Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" Then I should see an empty board Game with ID xxxxxxxx not found (RuntimeException) 1 scenario (1 failed) 3 steps (2 passed, 1 failed) 0m0.03s (10.87Mb)

Slide 62

Slide 62 text

Are We There Yet?

Slide 63

Slide 63 text

Behaviour Driven Development

Slide 64

Slide 64 text

PHPSpec: red-green-refactor class StartGameHandlerSpec extends ObjectBehavior { function it_should_start_a_game( GameRepository $gameRepository, Game $game ) { $command = new StartGameCommand(); $this->handle($command); $gameRepository->add($game)->shouldHaveBeenCalled(); } }

Slide 65

Slide 65 text

PHPSpec: red-green-refactor class StartGameHandlerSpec extends ObjectBehavior { function let( GameRepository $gameRepository, GameFactory $gameFactory, UuidInterface $gameId, Game $game ) { $this->beConstructedWith($gameRepository, $gameFactory); $gameFactory->create($gameId)->willReturn($game); } }

Slide 66

Slide 66 text

PHPSpec: red-green-refactor Scenario: Choose to go first Given I have not started a game yet When I start a game as player "X" Then I should see an empty board 1 scenario (1 pending) 3 steps (3 passed) 0m0.03s (10.87Mb)

Slide 67

Slide 67 text

PHPSpec: red-green-refactor Use cases: ● StartGameCommand ● GetGameQuery Ports: ● GameRepository Adapters: ● FakeGameRepository

Slide 68

Slide 68 text

Next Scenario

Slide 69

Slide 69 text

Next Scenario ● Scenario: Make a move ● Test Driven Development ● Test Doubles ● Behaviour Driven Development

Slide 70

Slide 70 text

Scenario: Make a move # features/start-game.feature Scenario: Make a move Given I have started a game as player "X" When I make a move Then I should see a board with one symbol on it

Slide 71

Slide 71 text

Scenario: Make a move

Slide 72

Slide 72 text

Test Driven Development

Slide 73

Slide 73 text

Test Driven Development “Test-driven development is a way of managing fear during programming.” – Kent Beck ● Write new code only if you first have a failing automated test. ● Eliminate duplication.

Slide 74

Slide 74 text

Test Driven Development Red-Green-Refactor 1. Write a failing test 2. Make the test pass 3. Refactor

Slide 75

Slide 75 text

Test Driven Development Step 1: Write a failing test ● Arrange, Act, Assert ● Listen for design feedback ● Make it compile* *) make it run up to the first assertion.

Slide 76

Slide 76 text

Test Driven Development: Design Feedback ● Keep It Simple, Stupid (KISS) ● You Ain’t Gonna Need It (YAGNI) ● Don’t Repeat Yourself (DRY) ● Low Coupling

Slide 77

Slide 77 text

Test Driven Development Step 2: Make the test pass ● Fake it ● Triangulation ● Obvious implementation

Slide 78

Slide 78 text

Test Driven Development: Fake It function testOneAndOneIsTwo() { $c = add(1, 1); $this->assertEquals(2, $c); } function add($a, $b) { return 2; }

Slide 79

Slide 79 text

Test Driven Development: Eliminate Duplication function testOneAndOneIsTwo() { $c = add(1, 1); $this->assertEquals(1 + 1, $c); } function add($a, $b) { return 1 + 1; }

Slide 80

Slide 80 text

Test Driven Development: Triangulation function testOneAndTwoIsThree() { $c = add(1, 2); $this->assertEquals(3, $c); } function add($a, $b) { return 1 + $b; }

Slide 81

Slide 81 text

Test Driven Development Step 3: Refactor ● Remove duplication ● Improve naming ● Improve readability ● Preparatory refactoring

Slide 82

Slide 82 text

Scenario: Make a move

Slide 83

Slide 83 text

Test Doubles

Slide 84

Slide 84 text

Test Doubles “The word "mock" is sometimes used in an informal way to refer to the whole family of objects that are used in tests. They are called Test Doubles.” – Robert C. Martin ● Dummy ● Stub ● Fake ● Spy ● Mock

Slide 85

Slide 85 text

Test Doubles: Prophecy $prophet = new Prophet(); $testDouble = $prophet->prophesize(Authorizer::class); // set up test double $authorizer = $testDouble->reveal();

Slide 86

Slide 86 text

Test Doubles: PHPSpec function let(Authorizer $testDouble) { $this->beConstructedWith($testDouble); } function it_should_(Authorizer $testDouble) { // use test double }

Slide 87

Slide 87 text

Test Doubles: Dummy function let(Authorizer $dummy) { $this->beConstructedWith($dummy); } function it_should_start_without_logins() { $this->loginCount()->shouldBe(0); }

Slide 88

Slide 88 text

Test Doubles: Stub function it_should_count_logins(Authorizer $stub) { $stub->authorize(Argument::any(), Argument::any()) ->willReturn(true); $this->login('user', 'pass'); $this->loginCount()->shouldBe(1); }

Slide 89

Slide 89 text

Test Doubles: Fake function it_should_count_logins(Authorizer $fake) { $fake->authorize('Bob', 'bobspassword') ->willReturn(true); $this->login('Bob', 'bobspassword'); $this->loginCount()->shouldBe(1); }

Slide 90

Slide 90 text

Test Doubles: Spy function it_should_authorize_users(Authorizer $spy) { $this->login('user', 'pass'); $spy->authorize('user', 'pass') ->shouldHaveBeenCalled(); }

Slide 91

Slide 91 text

Test Doubles: Mock function it_should_authorize_users(Authorizer $mock) { $mock->authorize('user', 'pass') ->willReturn(true) ->shouldBeCalled(); $this->login('user', 'pass'); }

Slide 92

Slide 92 text

Scenario: Make a move

Slide 93

Slide 93 text

Behaviour Driven Development

Slide 94

Slide 94 text

Behaviour Driven Development ● Test method names should be sentences ● A simple sentence template keeps test methods focused ● An expressive test name is helpful when a test fails ● “Behaviour” is a more useful word than “test” ● JBehave PHPSpec emphasizes behaviour over testing ● Determine the next most important behaviour ● Requirements are behaviour, too ● BDD provides a “ubiquitous language” for analysis ● Acceptance criteria should be executable from Introducing BDD by Dan North

Slide 95

Slide 95 text

Test method names should be sentences phpunit --testdox BankAccountTest PHPUnit 5.3.0 by Sebastian Bergmann and contributors. BankAccount [x] Balance is initially zero [x] Balance cannot become negative

Slide 96

Slide 96 text

A simple sentence template keeps test methods focused phpunit --testdox BankAccountTest PHPUnit 5.3.0 by Sebastian Bergmann and contributors. BankAccount [x] Should initially have a balance of zero [x] Should never have a negative balance

Slide 97

Slide 97 text

An expressive test name is helpful when a test fails ● I had introduced a bug. Bad me. Solution: Fix the bug. ● The intended behaviour was still relevant but had moved elsewhere. Solution: Move the test and maybe change it. ● The behaviour was no longer correct – the premise of the system had changed. Solution: Delete the test.

Slide 98

Slide 98 text

“Behaviour” is a more useful word than “test” Helps to answer questions about TDD: ● What should you call your test? ● How much should you cover in one test? ● When a test fails, what should you do?

Slide 99

Slide 99 text

PHPSpec emphasizes behaviour over testing ● Specification ● Example ● Describe ● Behaviour ● Matcher ● Expectation The word “test” is nowhere to be found.

Slide 100

Slide 100 text

Determine the next most important behaviour “The customer doesn’t want your code, they want the value of the software.” – Marcello Duarte ● Determine what objects should do before you decide how they should do it. (See also: CRC cards.) ● Objects expose behaviour, not data, so you have to tell them what to do with the data. (Tell, Don’t Ask)

Slide 101

Slide 101 text

CRC cards Class Responsibilities Collaborators

Slide 102

Slide 102 text

Requirements are behaviour, too Requirements are responsibilities at the application level

Slide 103

Slide 103 text

BDD provides a “ubiquitous language” for analysis Feature: ● As a ● I want ● So that Class: ● Arrange ● Act ● Assert Scenario: ● Given ● When ● Then Story BDD Spec BDD

Slide 104

Slide 104 text

Acceptance criteria should be executable Gherkin

Slide 105

Slide 105 text

Scenario: Make a move

Slide 106

Slide 106 text

Are We There Yet?

Slide 107

Slide 107 text

Behaviour Driven Development

Slide 108

Slide 108 text

Test Terminology

Slide 109

Slide 109 text

Test Terminology

Slide 110

Slide 110 text

Unit Tests

Slide 111

Slide 111 text

Integration Tests

Slide 112

Slide 112 text

Acceptance Tests

Slide 113

Slide 113 text

System Tests

Slide 114

Slide 114 text

Component Tests Limit the scope of the exercised software to a portion of the system under test

Slide 115

Slide 115 text

Functional Tests Verify correct that the correct behaviour is performed

Slide 116

Slide 116 text

Non-Functional Tests Check for requirements like performance and security

Slide 117

Slide 117 text

Smoke Tests You plug in a new board and turn on the power. If you see smoke coming from the board, turn off the power.

Slide 118

Slide 118 text

Learning Tests Instead of just using a new method or a new class, we write a little test that verifies that the API works as expected.

Slide 119

Slide 119 text

Regression Tests Verify that software still performs correctly after a change.

Slide 120

Slide 120 text

System Test

Slide 121

Slide 121 text

System Test # behat.yml default: suites: acceptance: contexts: - FeatureContext

Slide 122

Slide 122 text

System Test # behat.yml default: suites: system: contexts: - WebFeatureContext - Behat\MinkExtension\Context\MinkContext filters: tags: '@critical'

Slide 123

Slide 123 text

System Test # behat.yml default: extensions: Behat\MinkExtension: base_url: http://192.168.33.99/app_dev.php sessions: default: goutte: ~

Slide 124

Slide 124 text

System Test # features/start-game.feature @critical Scenario: Make a move Given I have started a game as player "X" When I make a move Then I should see a board with one symbol on it

Slide 125

Slide 125 text

System Test /** * @BeforeScenario */ public function loadMinkExtension(BeforeScenarioScope $scope) { $environment = $scope->getEnvironment(); $this->minkContext = $environment->getContext(MinkContext::class); }

Slide 126

Slide 126 text

System Test /** * @Given I have started a game as player :playerName */ public function iHaveStartedAGameAsPlayer($playerName) { $this->minkContext->visit('/'); $this->minkContext->pressButton(''); }

Slide 127

Slide 127 text

Integration Test

Slide 128

Slide 128 text

Integration Test ● Overloaded term ● Test an abstraction that we own but have implemented with some third-party package ● Don’t mock what you don’t own

Slide 129

Slide 129 text

Integration Test // tests/AppBundle/Repository/DoctrineGameRepositoryTest.php class DoctrineGameRepositoryTest extends KernelTestCase { /** * @test */ public function shouldGetGames() { } }

Slide 130

Slide 130 text

Integration Test /** * @test */ public function shouldGetGames() { static::bootKernel(); $entityManager = static::$kernel->getContainer() ->get('doctrine.orm.default_entity_manager'); $repository = $entityManager->getRepository(Game::class); $gameId = Uuid::uuid4(); $game = $repository->get($gameId); $this->assertInstanceOf(Game::class, $game); }

Slide 131

Slide 131 text

Integration Test /vagrant$ vendor/bin/phpunit PHPUnit 5.3.2 by Sebastian Bergmann and contributors. .E 2 / 2 (100%) Time: 1.35 seconds, Memory: 18.00Mb There was 1 error: 1) AppBundle\Repository\DoctrineGameRepositoryTest::shouldGetGames Doctrine\Common\Persistence\Mapping\MappingException: The class 'Application\Game\Game' was not found in the chain configured namespaces FAILURES! Tests: 2, Assertions: 2, Errors: 1.

Slide 132

Slide 132 text

Integration Test doctrine: orm: auto_generate_proxy_classes: "%kernel.debug%" naming_strategy: doctrine.orm.naming_strategy.underscore mappings: app: mapping: true type: yml dir: "%kernel.root_dir%/../src/AppBundle/Resources" prefix: "Application" is_bundle: false

Slide 133

Slide 133 text

Integration Test # src/AppBundle/Resources/Game.Game.orm.yml Application\Game\Game: type: entity id: id: type: string

Slide 134

Slide 134 text

Integration Test # src/AppBundle/Resources/Game.Game.orm.yml Application\Game\Game: type: entity repositoryClass: \AppBundle\Repository\DoctrineGameRepository id: id: type: string

Slide 135

Slide 135 text

Integration Test public function setUp() { static::bootKernel(); $application = new Application(static::$kernel); $application->setAutoExit(false); $input = new StringInput('doctrine:schema:create'); $output = new BufferedOutput(); $exitCode = $application->run($input, $output); $this->assertEquals(0, $exitCode, $output->fetch()); }

Slide 136

Slide 136 text

Integration Test # app/config/config_test.yml doctrine: dbal: driver: pdo_sqlite

Slide 137

Slide 137 text

Integration Test doctrine: dbal: types: uuid_binary: class: Ramsey\Uuid\Doctrine\UuidBinaryType

Slide 138

Slide 138 text

Integration Test /** * @test */ public function shouldGetGames() { // ... $this->assertInstanceOf(Game::class, $game); $this->assertInstanceOf(Uuid::class, $game->getId()); $this->assertInstanceOf(Board::class, $game->getBoard()); }

Slide 139

Slide 139 text

Integration Test // tests/AppBundle/Repository/DoctrineGameRepositoryTest.php class DoctrineGameRepositoryTest extends KernelTestCase { /** * @test */ public function shouldFailToGetNonExistentGame() { $this->expectException(RuntimeException::class); } }

Slide 140

Slide 140 text

Integration Test // tests/AppBundle/Repository/DoctrineGameRepositoryTest.php class DoctrineGameRepositoryTest extends KernelTestCase { /** * @test */ public function shouldAddGames() { } }

Slide 141

Slide 141 text

Integration Test // tests/AppBundle/Controller/DefaultControllerTest.php class DefaultControllerTest extends WebTestCase { public function testIndex() { $client = static::createClient(); $crawler = $client->request('GET', '/'); $this->assertEquals(200, $client->getResponse()->getStatusCode()); $this->assertCount(1, $crawler->filter('form[action$="/start"]')); $this->assertCount(1, $crawler->filter('form[action$="/start"] button')); } }

Slide 142

Slide 142 text

Integration Test public function shouldStartAGame() { $prophet = new Prophet(); $startGameHandler = $prophet->prophesize(StartGameHandler::class); $client = static::createClient(); $client->getKernel()->getContainer() ->set('app.start_game_handler', $startGameHandler->reveal()); $client->request('POST', '/start'); $startGameHandler->handle(Argument::type(StartGameCommand::class)) ->shouldHaveBeenCalled(); }

Slide 143

Slide 143 text

Integration Test // tests/AppBundle/AppBundleTest public function shouldExposeStartGameHandler() { static::bootKernel(); $startGameHandler = static::$kernel->getContainer()->get('app.start_game_handler'); $this->assertInstanceOf(StartGameHandler::class, $startGameHandler); }

Slide 144

Slide 144 text

Next Feature

Slide 145

Slide 145 text

Behaviour Driven Development # features/play-against-computer.feature Feature: Play against the computer In order to practice by myself As a player I want to play against the computer Scenario: Choose to go second Given I have not started a game yet When I start a game as player "O" Then I should see a board with one symbol on it Scenario: Computer makes the second move Given I have started a game as player "X" When I make a move Then I should see a board with two symbols on it

Slide 146

Slide 146 text

Round Up

Slide 147

Slide 147 text

Overview ● User Stories ● Hexagonal Architecture ● Test Driven Development ● Test Doubles ● Behaviour Driven Development ● Test Terminology

Slide 148

Slide 148 text

Test Driven Development ● Writing tests is a divergent process ● Making a test pass is a convergent process ● Alternate between formulating a problem and finding a solution

Slide 149

Slide 149 text

Hexagonal Architecture ● The application core is about business value ● Adapters are about getting things to work ● Alternate between the business domain and Google

Slide 150

Slide 150 text

Behaviour Driven Development ● Unit tests provide inner quality ● Acceptance tests provide outer quality ● Alternate between what the system is and what the system does

Slide 151

Slide 151 text

thnx @scataco