$30 off During Our Annual Pro Sale. View Details »

Adoptez le TDD sur vos projets Symfony2 existants

Adoptez le TDD sur vos projets Symfony2 existants

Benjamin Grandfond

April 04, 2013
Tweet

More Decks by Benjamin Grandfond

Other Decks in Programming

Transcript

  1. ADOPTEZ LE TDD SUR
    VOS PROJETS
    SYMFONY2 EXISTANTS

    View Slide

  2. Créer, refactoriser, corriger du code…

    View Slide

  3. Telle est la mission des développeurs

    View Slide

  4. On a tous peur d’introduire des bugs

    View Slide

  5. Si seulement il y avait eu de bons tests !

    View Slide

  6. Adoptez le TDD

    View Slide

  7. Benjamin Grandfond
    Technical team manager
     benja-M-1  benjaM1
    7

    View Slide

  8. LE TDD
    8
    Test-driven Development

    View Slide

  9. "Clean code that works"
    Ron Je!ries
    un des trois fondateurs de l’ «Extreme Programming» (XP)
    9

    View Slide

  10. RED
    REFACTOR
    1. écrire un test
    simple qui échoue
    3. éliminer la duplication
    GREEN
    2. écrire le minimum de
    code pour faire
    passer le test
    Mantra
    10

    View Slide

  11. 11
    + moins de debug
    + code découplé
    + plus de con!ance
    - prend du temps (au début)
    - plus de code (?)
    Avantages
    Inconvénients

    View Slide

  12. 12
    • patience, courage, persévérance
    • intégration continue
    • exécution des tests rapide
    • qualité des tests (lisibilité, conventions...)
    Pré requis pour le succès

    View Slide

  13. Suite de Fibonacci
    class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    public function testFibonacci()
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals(0, $fibonacci->fib(0));
    }
    }
    RED
    13

    View Slide

  14. class Fibonacci
    {
    public function fib($value)
    {
    return 0;
    }
    }
    Fake it!
    Suite de Fibonacci
    GREEN
    14

    View Slide

  15. class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    public function testFibonacci()
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals(0, $fibonacci->fib(0));
    $this->assertEquals(1, $fibonacci->fib(1));
    }
    }
    Suite de Fibonacci
    RED
    15
    class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    public function testFibonacci()
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals(0, $fibonacci->fib(0));
    $this->assertEquals(1, $fibonacci->fib(1));
    }
    }
    duplication

    View Slide

  16. class Fibonacci
    {
    public function fib($value)
    {
    if ($value === 0) {
    return 0;
    }
    return 1;
    }
    }
    Suite de Fibonacci
    GREEN
    16

    View Slide

  17. class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    /**
    * @dataProvider getValues
    */
    public function testFibonacci($value, $result)
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals($result, $fibonacci->fib($value));
    }
    public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    );
    }
    }
    Suite de Fibonacci
    REFACTOR
    17

    View Slide

  18. class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    /**
    * @dataProvider getValues
    */
    public function testFibonacci($value, $result)
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals($result, $fibonacci->fib($value));
    }
    public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    );
    }
    }
    Suite de Fibonacci
    REFACTOR
    17

    View Slide

  19. class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    /**
    * @dataProvider getValues
    */
    public function testFibonacci($value, $result)
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals($result, $fibonacci->fib($value));
    }
    public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    );
    }
    }
    Suite de Fibonacci
    REFACTOR
    17

    View Slide

  20. class FibonacciTest extends \PHPUnit_Framework_TestCase
    {
    /**
    * @dataProvider getValues
    */
    public function testFibonacci($value, $result)
    {
    $fibonacci = new Fibonacci();
    $this->assertEquals($result, $fibonacci->fib($value));
    }
    public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    );
    }
    }
    Suite de Fibonacci
    REFACTOR
    17

    View Slide

  21. public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    array(2, 1),
    );
    }
    Suite de Fibonacci
    GREEN
    18

    View Slide

  22. public function getValues()
    {
    return array(
    array(0, 0),
    array(1, 1),
    array(2, 1),
    array(3, 2),
    );
    }
    RED
    Triangulation
    Suite de Fibonacci
    19

    View Slide

  23. class Fibonacci
    {
    public function fib($value)
    {
    if ($value === 0) {
    return 0;
    }
    if ($value <= 2) {
    return 1;
    }
    return 2;
    }
    }
    GREEN Suite de Fibonacci
    20

    View Slide

  24. class Fibonacci
    {
    public function fib($value)
    {
    if ($value === 0) {
    return 0;
    }
    if ($value <= 2) {
    return 1;
    }
    return 1 + 1;
    }
    }
    Suite de Fibonacci
    REFACTOR
    21

    View Slide

  25. class Fibonacci
    {
    public function fib($value)
    {
    if ($value === 0) {
    return 0;
    }
    if ($value <= 2) {
    return 1;
    }
    return $this->fib($value - 1) + $this->fib($value - 2);
    }
    }
    REFACTOR Suite de Fibonacci
    22

    View Slide

  26. METTRE EN PLACE LE
    TDD DANS VOS
    PROJETS SYMFONY2
    23

    View Slide

  27. Créer une nouvelle page
    24

    View Slide

  28. • récupérer les experts dans la base de données
    • créer un template qui a!che la liste
    • créer un contrôleur
    • ajouter une nouvelle route
    Ce qu’on veut :
    Créer une nouvelle page
    25

    View Slide

  29. 1. code retour de la page = 200
    2. la réponse contient une liste
    3. le nombre de li = le nombre d’experts dans la base
    Ce qu’on va tester :
    test list
    Créer une nouvelle page
    26

    View Slide

  30. Créer une nouvelle page
    RED
    27
    namespace Theodo\Bundle\ExpertBundle\Tests\Controller;
    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    /**
    * ExpertControllerTest
    *
    * @author Benjamin Grandfond
    * @group functional
    */
    class ExpertControllerTest extends WebTestCase
    {
    public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    $crawler = $client->request('GET', '/experts');
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertGreaterThan(1, $crawler->filter('li')->count());
    }
    }

    View Slide

  31. Créer une nouvelle page
    RED
    27
    namespace Theodo\Bundle\ExpertBundle\Tests\Controller;
    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    /**
    * ExpertControllerTest
    *
    * @author Benjamin Grandfond
    * @group functional
    */
    class ExpertControllerTest extends WebTestCase
    {
    public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    $crawler = $client->request('GET', '/experts');
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertGreaterThan(1, $crawler->filter('li')->count());
    }
    }
    TIP

    View Slide

  32. Créer une nouvelle page
    28
    TIP

    View Slide

  33. RED
    Créer une nouvelle page
    29

    View Slide

  34. 1. créer la route
    2. créer le contrôleur et l’action
    3. créer un template avec une liste et deux éléments
    Il faut que le test passe avec un minimum de code
    Créer une nouvelle page
    30

    View Slide

  35. RED
    namespace Theodo\Bundle\ExpertBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration as Extra;
    class ExpertController extends Controller
    {
    /**
    * @Extra\Route("/experts")
    * @Extra\Template()
    */
    public function listAction()
    {
    return array();
    }
    }
    {% extends '::base.html.twig' %}
    {% block body %}

    Marek
    Pierre-Henri

    {% endblock %}
    Créer une nouvelle page
    31

    View Slide

  36. GREEN
    Créer une nouvelle page
    32

    View Slide

  37. {% extends '::base.html.twig' %}
    {% block body %}

    {% for expert in experts %}
    {{ expert }}
    {% endfor %}

    {% endblock %}
    On fait en sorte que les experts
    soient dynamiques
    public function listAction()
    {
    $experts = array(
    'Marek',
    'Pierre-Henri',
    );
    return array(
    'experts' => $experts
    );
    }
    REFACTOR
    Créer une nouvelle page
    33

    View Slide

  38. REFACTOR
    Créer une nouvelle page
    34

    View Slide

  39. ✔ code retour de la page = 200
    ✔ la réponse contient une liste
    ✘ le nombre de li = le nombre d’experts dans la base
    Ce qu’on a testé :
    Créer une nouvelle page
    35

    View Slide

  40. Utilisation de
    !xtures
    36

    View Slide

  41. • DoctrineFixturesBundle
    https://github.com/doctrine/DoctrineFixturesBundle
    • Faker
    https://github.com/fzaninotto/Faker
    • Alice
    https://github.com/nelmio/alice
    Les outils à utiliser
    37

    View Slide

  42. REFACTOR
    # app/config/config_test.yml
    doctrine:
    dbal:
    driver: pdo_sqlite
    host: localhost
    port: ~
    dbname: symfony
    user: root
    password: ~
    path: %kernel.root_dir%/cache/database.db
    Créer une nouvelle page
    38

    View Slide

  43. /**
    * Generates the schema to use on the test environment.
    *
    * @throws \Doctrine\DBAL\Schema\SchemaException
    */
    protected static function generateSchema()
    {
    if (null === static::$kernel) {
    static::$kernel = static::createKernel();
    }
    $em = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
    // Get the metadata of the application to create the schema.
    $metadata = $em->getMetadataFactory()->getAllMetadata();
    if (!empty($metadata)) {
    $tool = new \Doctrine\ORM\Tools\SchemaTool($em);
    $tool->dropDatabase();
    $tool->createSchema($metadata);
    } else {
    throw new \Doctrine\DBAL\Schema\SchemaException(
    'No Metadata Classes to process.'
    );
    }
    }
    REFACTOR
    Créer une nouvelle page
    39

    View Slide

  44. public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    self::generateSchema();
    $loader = new \Nelmio\Alice\Loader\Base();
    $objects = $loader->load($this->getExperts());
    $persister = new \Nelmio\Alice\ORM\Doctrine(
    static::$kernel->getContainer()->get('doctrine.orm.entity_manager')
    );
    $persister->persist($objects);
    $crawler = $client->request('GET', '/Experts');
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertGreaterThan(1, $crawler->filter('li')->count());
    }
    private function getExperts()
    {
    return array(
    'Theodo\Bundle\ExpertBundle\Entity\Expert' => array(
    'expert{1..5}' => array(
    'firstName' => '',
    'lastName' => '',
    'username' => ''
    ),
    )
    );
    }
    Créer une nouvelle page
    REFACTOR
    40

    View Slide

  45. REFACTOR
    Créer une nouvelle page
    private function getExperts()
    {
    return array(
    'Theodo\Bundle\ExpertBundle\Entity\Expert' => array(
    'expert{1..5}' => array(
    'firstName' => '',
    'lastName' => '',
    'username' => ''
    ),
    )
    );
    }

    View Slide

  46. REFACTOR
    Créer une nouvelle page
    private function getExperts()
    {
    return array(
    'Theodo\Bundle\ExpertBundle\Entity\Expert' => array(
    'expert{1..5}' => array(
    'firstName' => '',
    'lastName' => '',
    'username' => ''
    ),
    )
    );
    }
    créé 5 experts

    View Slide

  47. REFACTOR
    Créer une nouvelle page
    private function getExperts()
    {
    return array(
    'Theodo\Bundle\ExpertBundle\Entity\Expert' => array(
    'expert{1..5}' => array(
    'firstName' => '',
    'lastName' => '',
    'username' => ''
    ),
    )
    );
    }
    formateurs Faker
    créé 5 experts

    View Slide

  48. REFACTOR
    public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    self::generateSchema();
    $loader = new \Nelmio\Alice\Loader\Base();
    $objects = $loader->load($this->getExperts());
    $persister = new \Nelmio\Alice\ORM\Doctrine(
    static::$kernel->getContainer()->get('doctrine.orm.entity_manager')
    );
    $persister->persist($objects);
    // ...
    }
    Créer une nouvelle page

    View Slide

  49. REFACTOR
    public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    self::generateSchema();
    $loader = new \Nelmio\Alice\Loader\Base();
    $objects = $loader->load($this->getExperts());
    $persister = new \Nelmio\Alice\ORM\Doctrine(
    static::$kernel->getContainer()->get('doctrine.orm.entity_manager')
    );
    $persister->persist($objects);
    // ...
    }
    Créer une nouvelle page
    transforme les fixtures
    en objets

    View Slide

  50. REFACTOR
    public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    self::generateSchema();
    $loader = new \Nelmio\Alice\Loader\Base();
    $objects = $loader->load($this->getExperts());
    $persister = new \Nelmio\Alice\ORM\Doctrine(
    static::$kernel->getContainer()->get('doctrine.orm.entity_manager')
    );
    $persister->persist($objects);
    // ...
    }
    Créer une nouvelle page
    transforme les fixtures
    en objets
    persiste les objets dans la base

    View Slide

  51. REFACTOR
    /**
    * @Extra\Route("/experts")
    * @Extra\Template()
    */
    public function listAction()
    {
    $experts = $this->getDoctrine()
    ->getRepository('TheodoBundleExpertBundle:Expert')
    ->findAll()
    ;
    return array(
    'experts' => $experts
    );
    }
    Créer une nouvelle page
    43

    View Slide

  52. REFACTOR
    Créer une nouvelle page
    44

    View Slide

  53. ✔ code retour de la page = 200
    ✔ la réponse contient une liste
    ✔ le nombre de li = le nombre d’experts dans la base
    Ce qu’on a testé :
    Créer une nouvelle page
    45

    View Slide

  54. Réutiliser l’existant ...
    qui n’a pas de tests
    46

    View Slide

  55. Réutiliser l’existant
    public function listAction()
    {
    $experts = $this->getDoctrine()
    ->getRepository('TheodoBundleExpertBundle:Expert')
    ->findAll()
    ;
    $expert = new Expert();
    $builder = $this->createFormBuilder($Expert);
    $form = $builder
    ->add('firstName')
    ->add('lastName')
    ->add('username')
    ->getForm();
    ;
    if ($this->getRequest()->isMethod('post')) {
    $form->bind($this->getRequest());
    if ($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($form->getData());
    $em->flush();
    // ici on envoie un email, au nouvel expert
    // on créé ses comptes sur tous les services utilisés par la boite etc...
    return $this->redirect($this->generateUrl('theodoexpertbundle_expert_list'));
    }
    }
    return array('experts' => $experts, 'form’ => $form->createView());
    }
    47
    Contrôleur

    View Slide

  56. Réutiliser l’existant
    public function listAction()
    {
    $experts = $this->getDoctrine()
    ->getRepository('TheodoBundleExpertBundle:Expert')
    ->findAll()
    ;
    $expert = new Expert();
    $builder = $this->createFormBuilder($Expert);
    $form = $builder
    ->add('firstName')
    ->add('lastName')
    ->add('username')
    ->getForm();
    ;
    if ($this->getRequest()->isMethod('post')) {
    $form->bind($this->getRequest());
    if ($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($form->getData());
    $em->flush();
    // ici on envoie un email, au nouvel expert
    // on créé ses comptes sur tous les services utilisés par la boite etc...
    return $this->redirect($this->generateUrl('theodoexpertbundle_expert_list'));
    }
    }
    return array('experts' => $experts, 'form’ => $form->createView());
    }
    47
    Contrôleur
    création du formulaire

    View Slide

  57. Réutiliser l’existant
    public function listAction()
    {
    $experts = $this->getDoctrine()
    ->getRepository('TheodoBundleExpertBundle:Expert')
    ->findAll()
    ;
    $expert = new Expert();
    $builder = $this->createFormBuilder($Expert);
    $form = $builder
    ->add('firstName')
    ->add('lastName')
    ->add('username')
    ->getForm();
    ;
    if ($this->getRequest()->isMethod('post')) {
    $form->bind($this->getRequest());
    if ($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $em->persist($form->getData());
    $em->flush();
    // ici on envoie un email, au nouvel expert
    // on créé ses comptes sur tous les services utilisés par la boite etc...
    return $this->redirect($this->generateUrl('theodoexpertbundle_expert_list'));
    }
    }
    return array('experts' => $experts, 'form’ => $form->createView());
    }
    47
    Contrôleur
    création du formulaire
    sauvegarde l’expert

    View Slide

  58. {% extends '::base.html.twig' %}
    {% block body %}

    {% for expert in experts %}
    {{ expert }}
    {% endfor %}

    method="post">
    {{ form_errors(form) }}
    {{ form_widget(form) }}
    Save

    {% endblock %}
    Réutiliser l’existant
    48
    Template

    View Slide

  59. Ce qu’on va tester :
    • l’a!chage de la liste et du formulaire
    • la sauvegarde d’un nouvel expert
    test list
    Réutiliser l’existant
    49

    View Slide

  60. Ce qu’on va tester :
    • l’a!chage de la liste et du formulaire
    • la sauvegarde d’un nouvel expert
    test list
    Réutiliser l’existant
    49
    • la page d’édition
    • la sauvegarde de l’édition d’un expert
    new
    new

    View Slide

  61. public function testShouldDisplayTheExpertsList()
    {
    $client = self::createClient();
    self::generateSchema();
    // load fixtures ...
    $crawler = $client->request('GET', '/experts');
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertGreaterThan(1, $crawler->filter('li')->count());
    $this->assertCount(1, $crawler->filter('form'));
    }
    Note : ceci est un nouveau test !
    Tester l’existant
    50
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  62. RED GREEN
    ou ?
    Tester l’existant
    51

    View Slide

  63. Tester l’existant
    52
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  64. 53

    View Slide

  65. 53
    Ne faites pas con!ance aux tests
    écrits après le code, testez les tests !

    View Slide

  66. {% extends '::base.html.twig' %}
    {% block body %}


    {% endblock %}
    Tester les tests
    54
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  67. 55
    Tester les tests
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  68. {% extends '::base.html.twig' %}
    {% block body %}

    {% for expert in experts %}
    {{ expert }}
    {% endfor %}

    {% endblock %}
    Tester les tests
    56
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  69. 57
    Tester les tests
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  70. Tester les tests
    58
    {% extends '::base.html.twig' %}
    {% block body %}

    {% for expert in experts %}
    {{ expert }}
    {% endfor %}

    method="post">
    {{ form_errors(form) }}
    {{ form_widget(form) }}
    Save

    {% endblock %}
    ✘ l’a!chage de la liste et du formulaire

    View Slide

  71. 59
    Tester les tests
    GREEN
    ✔ l’a!chage de la liste et du formulaire

    View Slide

  72. Véri!ons que la sauvegarde du
    formulaire fonctionne !
    60

    View Slide

  73. public function testShouldSaveANewExpert()
    {
    $client = self::createClient();
    $csrfToken = $client->getContainer()
    ->get('form.csrf_provider')
    ->generateCsrfToken('unknown');
    $client->request('POST', '/experts', array(
    'form' => array(
    'firstName' => 'Benjamin',
    'lastName' => 'Grandfond',
    'username' => 'benjaming',
    '_token' => $csrfToken,
    )
    ));
    $this->assertEquals(302, $client->getResponse()->getStatusCode());
    $crawler = $client->followRedirect();
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertRegExp('~Benjamin~', $crawler->filter('li')->text());
    }
    61
    Tester l’existant
    ✘ sauvegarde d’un nouvel expert

    View Slide

  74. public function testShouldSaveANewExpert()
    {
    $client = self::createClient();
    $csrfToken = $client->getContainer()
    ->get('form.csrf_provider')
    ->generateCsrfToken('unknown');
    $client->request('POST', '/experts', array(
    'form' => array(
    'firstName' => 'Benjamin',
    'lastName' => 'Grandfond',
    'username' => 'benjaming',
    '_token' => $csrfToken,
    )
    ));
    $this->assertEquals(302, $client->getResponse()->getStatusCode());
    $crawler = $client->followRedirect();
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    $this->assertRegExp('~Benjamin~', $crawler->filter('li')->text());
    }
    61
    Tester l’existant
    TIP
    ✘ sauvegarde d’un nouvel expert

    View Slide

  75. # app/config/config_test.yml
    parameters:
    form.csrf_provider.class: 'Theodo\Bundle\ExpertBundle\Form\Extension\Csrf
    \CsrfProvider\MockCsrfProvider'
    Mockez les services en changeant la classe
    utilisée par le container.
    TIP
    62
    Tester l’existant

    View Slide

  76. namespace Theodo\Bundle\ExpertBundle\Form\Extension\Csrf\CsrfProvider;
    use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
    class MockCsrfProvider implements CsrfProviderInterface
    {
    public function generateCsrfToken($intention)
    {
    return $intention;
    }
    public function isCsrfTokenValid($intention, $token)
    {
    return $intention == $token;
    }
    }
    TIP
    63
    Tester l’existant

    View Slide

  77. 64
    Tester l’existant
    ✔ la sauvegarde d’un nouvel expert

    View Slide

  78. Encore une fois : Ne faites pas con!ance
    aux tests écrits après le code,
    testez les tests !
    65

    View Slide

  79. ✔ l’a!chage de la liste et du formulaire
    ✔ la sauvegarde d’un nouvel expert
    ✘ la page d’édition
    ✘ la sauvegarde de l’édition d’un expert
    Ce qu’on a testé :
    test list
    66
    Réutiliser l’existant

    View Slide

  80. • validité du formulaire
    • retourne une
    redirection
    • retourne le formulaire
    erroné
    • bind le formulaire avec
    la request
    • sauvegarder l’entité du
    formulaire
    Refactorisation : Création d’un handler pour
    sauvegarder un expert.
    Ce qu’on va tester :
    test list
    Sauvegarde du formulaire
    67

    View Slide

  81. RED
    namespace Theodo\Bundle\ExpertBundle\Tests\Form\Handler;
    use Theodo\Bundle\ExpertBundle\Form\Handler\ExpertFormHandler;
    class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase
    {
    public function testShouldHandleAnUnsuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $handler = new ExpertFormHandler();
    $this->assertEquals($form, $handler->handle($form, $request));
    }
    }
    Refactoriser l’existant
    68
    ✘ retourne le formulaire erroné

    View Slide

  82. 69
    Mock ?!

    View Slide

  83. 70
    Un mock est un objet qui simule un autre objet et
    permet de contrôler les interactions avec l‘objet testé.
    • véri"er le nombre d’appels d’une méthode
    • spéci"er le retour de la méthode
    • véri"er les paramètres d’une méthode
    • ...

    View Slide

  84. 71
    • PHPUnit Mock Object
    https://github.com/sebastianbergmann/phpunit-mock-objects
    • Mockery
    https://github.com/padraic/mockery
    • Phake
    https://github.com/mlively/Phake
    Les frameworks

    View Slide

  85. RED
    72
    Refactoriser l’existant
    ✘ retourne le formulaire erroné

    View Slide

  86. RED
    namespace Theodo\Bundle\ExpertBundle\Form\Handler;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\HttpFoundation\Request;
    class ExpertFormHandler
    {
    public function handle(FormInterface $form, Request $request)
    {
    return $form;
    }
    }
    73
    Refactoriser l’existant
    ✘ retourne le formulaire erroné

    View Slide

  87. RED
    namespace Theodo\Bundle\ExpertBundle\Form\Handler;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\HttpFoundation\Request;
    class ExpertFormHandler
    {
    public function handle(FormInterface $form, Request $request)
    {
    return $form;
    }
    }
    73
    Refactoriser l’existant
    ✘ retourne le formulaire erroné
    code minimum !

    View Slide

  88. GREEN
    74
    Refactoriser l’existant
    ✔ retourne le formulaire erroné

    View Slide

  89. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $form->expects($this->once())
    ->method('isValid')
    ->will($this->returnValue(true))
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request)
    $this->assertInstanceOf(
    'Symfony\Component\HttpFoundation\RedirectResponse',
    $response
    );
    $this->assertEquals('/experts', $response->getTargetUrl());
    }
    75
    Refactoriser l’existant
    ✘ validité du formulaire
    ✘ retourne une redirection

    View Slide

  90. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $form->expects($this->once())
    ->method('isValid')
    ->will($this->returnValue(true))
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request)
    $this->assertInstanceOf(
    'Symfony\Component\HttpFoundation\RedirectResponse',
    $response
    );
    $this->assertEquals('/experts', $response->getTargetUrl());
    }
    75
    Refactoriser l’existant
    ✘ validité du formulaire
    ✘ retourne une redirection
    le formulaire est
    valide

    View Slide

  91. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $form->expects($this->once())
    ->method('isValid')
    ->will($this->returnValue(true))
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request)
    $this->assertInstanceOf(
    'Symfony\Component\HttpFoundation\RedirectResponse',
    $response
    );
    $this->assertEquals('/experts', $response->getTargetUrl());
    }
    75
    Refactoriser l’existant
    ✘ validité du formulaire
    ✘ retourne une redirection
    le formulaire est
    valide
    handle() retourne une
    redirection vers /experts

    View Slide

  92. RED
    76
    Refactoriser l’existant
    ✘ validité du formulaire
    ✘ retourne une redirection

    View Slide

  93. RED
    namespace Theodo\Bundle\ExpertBundle\Form\Handler;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\RedirectResponse;
    class ExpertFormHandler
    {
    public function handle(FormInterface $form, Request $request)
    {
    if ($form->isValid()) {
    return new RedirectResponse("/experts");
    }
    return $form;
    }
    }
    77
    Refactoriser l’existant
    ✘ validité du formulaire
    ✘ retourne une redirection

    View Slide

  94. GREEN
    78
    Refactoriser l’existant
    ✔ validité du formulaire
    ✔ retourne une redirection

    View Slide

  95. REFACTOR
    public function testShouldHandleAnUnsuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request);
    // ...
    79
    Refactoriser l’existant
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request);
    // ...

    View Slide

  96. REFACTOR
    public function testShouldHandleAnUnsuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request);
    // ...
    79
    Refactoriser l’existant
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $handler = new ExpertFormHandler();
    $response = $handler->handle($form, $request);
    // ...
    Duplication

    View Slide

  97. REFACTOR
    class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase
    {
    protected $handler;
    public function setUp()
    {
    $this->handler = new ExpertFormHandler();
    }
    //...
    80
    Refactoriser l’existant

    View Slide

  98. REFACTOR
    public function getFormMock()
    {
    return $this
    ->getMockBuilder('Symfony\Component\Form\Test\FormInterface')
    ->disableOriginalConstructor()
    ->getMock();
    }
    81
    Refactoriser l’existant

    View Slide

  99. REFACTOR
    public function testShouldHandleAnUnsuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this->getFromMock();
    $response = $this->handler->handle($form, $request);
    // ...
    82
    Refactoriser l’existant
    public function testShouldHandleASuccessfulFormSubmission()
    {
    $request = new \Symfony\Component\HttpFoundation\Request();
    $form = $this->getFromMock();
    // ...
    $response = $this->handler->handle($form, $request);
    // ...

    View Slide

  100. REFACTOR
    83
    Refactoriser l’existant

    View Slide

  101. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $form->expects($this->once())
    ->method('bind')
    ->with($this->identicalTo($request))
    ;
    // ...
    }
    84
    Refactoriser l’existant
    ✘ bind le formulaire avec la request

    View Slide

  102. RED
    85
    Refactoriser l’existant
    ✘ bind le formulaire avec la request

    View Slide

  103. RED
    namespace Theodo\Bundle\ExpertBundle\Form\Handler;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\RedirectResponse;
    class ExpertFormHandler
    {
    public function handle(FormInterface $form, Request $request)
    {
    $form->bind($request);
    if ($form->isValid()) {
    return new RedirectResponse("/experts");
    }
    return $form;
    }
    }
    86
    Refactoriser l’existant
    ✘ bind le formulaire avec la request

    View Slide

  104. ✔ validité du formulaire
    ✔ retourne une
    redirection
    ✔ retourne le
    formulaire erroné
    ✔ bind le formulaire
    avec la request
    ✘ sauvegarder l’entité
    du formulaire
    Sauvegarde du formulaire
    test list
    87

    View Slide

  105. ✔ validité du formulaire
    ✔ retourne une
    redirection
    ✔ retourne le
    formulaire erroné
    ✔ bind le formulaire
    avec la request
    ✘ sauvegarder l’entité
    du formulaire
    Sauvegarde du formulaire
    test list
    87
    new
    ✘ génère la route avec
    le router

    View Slide

  106. GREEN
    88
    Refactoriser l’existant
    ✔ bind le formulaire avec la request

    View Slide

  107. RED
    89
    Refactoriser l’existant
    ✘ génère la route avec le router
    class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase
    {
    protected $handler;
    protected $router;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->handler = new ExpertFormHandler($this->router);
    }
    }

    View Slide

  108. RED
    89
    Refactoriser l’existant
    ✘ génère la route avec le router
    class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase
    {
    protected $handler;
    protected $router;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->handler = new ExpertFormHandler($this->router);
    }
    }
    mock le router

    View Slide

  109. RED
    89
    Refactoriser l’existant
    ✘ génère la route avec le router
    class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase
    {
    protected $handler;
    protected $router;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->handler = new ExpertFormHandler($this->router);
    }
    }
    mock le router
    injecte le router

    View Slide

  110. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $this->router->expects($this->once())
    ->method('generate')
    ->with($this->equalTo('theodoexpertbundle_expert_list')
    ->will($this->returnValue('/experts'))
    ;
    $response = $this->handler->handle($form, $request);
    // ...
    }
    90
    Refactoriser l’existant
    ✘ génère la route avec le router

    View Slide

  111. RED
    91
    Refactoriser l’existant
    ✘ génère la route avec le router

    View Slide

  112. RED
    namespace Theodo\Bundle\ExpertBundle\Form\Handler;
    use ...;
    use Symfony\Component\Routing\RouterInterface;
    class ExpertFormHandler
    {
    /**
    * @var \Symfony\Component\Routing\RouterInterface
    */
    protected $router;
    /**
    * @param RouterInterface $router
    */
    public function __construct(RouterInterface $router)
    {
    $this->router = $router;
    }
    // ...
    }
    92
    Refactoriser l’existant
    ✘ génère la route avec le router

    View Slide

  113. RED
    public function handle(FormInterface $form, Request $request)
    {
    $form->bind($request);
    if ($form->isValid()) {
    $url = $this->router->generate(
    'theodoexpertbundle_expert_list'
    );
    return new RedirectResponse($url);
    }
    return $form;
    }
    93
    Refactoriser l’existant
    ✘ génère la route avec le router

    View Slide

  114. GREEN
    94
    Refactoriser l’existant
    ✔ génère la route avec le router

    View Slide

  115. ✔ validité du formulaire
    ✔ retourne une
    redirection
    ✔ retourne le
    formulaire erroné
    ✔ bind le formulaire
    avec la request
    ✔ génère la route avec
    le router
    ✘ sauvegarder l’entité
    du formulaire
    Sauvegarde du formulaire
    95
    test list

    View Slide

  116. RED
    protected $em;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $this->handler = new ExpertFormHandler($this->router, $this->em);
    }
    96
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  117. RED
    protected $em;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $this->handler = new ExpertFormHandler($this->router, $this->em);
    }
    96
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    mock l’entity manager

    View Slide

  118. RED
    protected $em;
    public function setUp()
    {
    $this->router = $this->getMock('Symfony\Component\Routing\RouterInterface');
    $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
    ->disableOriginalConstructor()
    ->getMock()
    ;
    $this->handler = new ExpertFormHandler($this->router, $this->em);
    }
    96
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    mock l’entity manager
    injecte l’entity manager

    View Slide

  119. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $expert = new \Theodo\Bundle\ExpertBundle\Entity\Expert();
    $form->expects($this->once())
    ->method('getData')
    ->will($this->returnValue($expert));
    // ...
    $this->em->expects($this->once())
    ->method('persist')
    ->with($this->identicalTo($expert))
    ;
    $this->em->expects($this->once())
    ->method('flush');
    // ...
    97
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  120. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $expert = new \Theodo\Bundle\ExpertBundle\Entity\Expert();
    $form->expects($this->once())
    ->method('getData')
    ->will($this->returnValue($expert));
    // ...
    $this->em->expects($this->once())
    ->method('persist')
    ->with($this->identicalTo($expert))
    ;
    $this->em->expects($this->once())
    ->method('flush');
    // ...
    97
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    getData retournera
    un expert

    View Slide

  121. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $expert = new \Theodo\Bundle\ExpertBundle\Entity\Expert();
    $form->expects($this->once())
    ->method('getData')
    ->will($this->returnValue($expert));
    // ...
    $this->em->expects($this->once())
    ->method('persist')
    ->with($this->identicalTo($expert))
    ;
    $this->em->expects($this->once())
    ->method('flush');
    // ...
    97
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    getData retournera
    un expert
    cet expert sera
    persisté

    View Slide

  122. RED
    public function testShouldHandleASuccessfulFormSubmission()
    {
    // ...
    $expert = new \Theodo\Bundle\ExpertBundle\Entity\Expert();
    $form->expects($this->once())
    ->method('getData')
    ->will($this->returnValue($expert));
    // ...
    $this->em->expects($this->once())
    ->method('persist')
    ->with($this->identicalTo($expert))
    ;
    $this->em->expects($this->once())
    ->method('flush');
    // ...
    97
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    getData retournera
    un expert
    cet expert sera
    persisté
    l’em sera "ushé

    View Slide

  123. RED
    98
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  124. RED
    class ExpertFormHandler
    {
    /** @var \Symfony\Component\Routing\RouterInterface */
    protected $router;
    /** @var \Doctrine\ORM\EntityManager */
    protected $manager;
    /**
    * @param RouterInterface $router
    * @param EntityManager $manager
    */
    public function __construct(RouterInterface $router, EntityManager $manager)
    {
    $this->router = $router;
    $this->manager = $manager;
    }
    99
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  125. RED
    class ExpertFormHandler
    {
    /** @var \Symfony\Component\Routing\RouterInterface */
    protected $router;
    /** @var \Doctrine\ORM\EntityManager */
    protected $manager;
    /**
    * @param RouterInterface $router
    * @param EntityManager $manager
    */
    public function __construct(RouterInterface $router, EntityManager $manager)
    {
    $this->router = $router;
    $this->manager = $manager;
    }
    99
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  126. RED
    class ExpertFormHandler
    {
    /** @var \Symfony\Component\Routing\RouterInterface */
    protected $router;
    /** @var \Doctrine\ORM\EntityManager */
    protected $manager;
    /**
    * @param RouterInterface $router
    * @param EntityManager $manager
    */
    public function __construct(RouterInterface $router, EntityManager $manager)
    {
    $this->router = $router;
    $this->manager = $manager;
    }
    99
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  127. RED
    class ExpertFormHandler
    {
    /** @var \Symfony\Component\Routing\RouterInterface */
    protected $router;
    /** @var \Doctrine\ORM\EntityManager */
    protected $manager;
    /**
    * @param RouterInterface $router
    * @param EntityManager $manager
    */
    public function __construct(RouterInterface $router, EntityManager $manager)
    {
    $this->router = $router;
    $this->manager = $manager;
    }
    99
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  128. RED
    public function handle(FormInterface $form, Request $request)
    {
    $form->bind($request);
    if ($form->isValid()) {
    $this->manager->persist($form->getData());
    $this->manager->flush();
    $route = 'theodoexpertbundle_expert_list';
    return new RedirectResponse($this->router->generate($route));
    }
    return $form;
    }
    100
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire

    View Slide

  129. RED
    public function handle(FormInterface $form, Request $request)
    {
    $form->bind($request);
    if ($form->isValid()) {
    $this->manager->persist($form->getData());
    $this->manager->flush();
    $route = 'theodoexpertbundle_expert_list';
    return new RedirectResponse($this->router->generate($route));
    }
    return $form;
    }
    100
    Refactoriser l’existant
    ✘ sauvegarder l’entité du formulaire
    persiste l’expert
    puis "ush

    View Slide

  130. 101
    GREEN
    Refactoriser l’existant
    ✔ sauvegarder l’entité du formulaire

    View Slide

  131. ✔ validité du formulaire
    ✔ retourne une
    redirection
    ✔ retourne le
    formulaire erroné
    ✔ bind le formulaire
    avec la request
    ✔ génère la route avec
    le router
    ✔ sauvegarder l’entité
    du formulaire
    Sauvegarde du formulaire
    102
    test list

    View Slide

  132. Maintenant on peut faire
    l’édition d’un expert en TDD !
    103

    View Slide

  133. Maintenant on peut faire
    l’édition d’un expert en TDD !
    103

    View Slide

  134. Conclusion
    • Soyez patient
    • Persévérez
    • (Ré)introduisez progressivement des tests
    • Testez ce qui est primordial
    • Refactorisez, corrigez, créez en TDD

    View Slide

  135. Questions ?
     benja-M-1  benjaM1
    [email protected]:benja-M-1/s"ive-paris-2013.git
    https://speakerdeck.com/benjam1/adoptez-le-tdd-sflive-paris-2013
    Venez me retrouvez au stand Theodo pour en savoir plus !
    105

    View Slide