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

Adoptez le TDD sur vos projets Symfony2 existants

Adoptez le TDD sur vos projets Symfony2 existants

79637b39737f884d090cac0d7c768351?s=128

Benjamin Grandfond

April 04, 2013
Tweet

Transcript

  1. ADOPTEZ LE TDD SUR VOS PROJETS SYMFONY2 EXISTANTS

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

  3. Telle est la mission des développeurs

  4. On a tous peur d’introduire des bugs

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

  6. Adoptez le TDD

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

  8. LE TDD 8 Test-driven Development

  9. "Clean code that works" Ron Je!ries un des trois fondateurs

    de l’ «Extreme Programming» (XP) 9
  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
  11. 11 + moins de debug + code découplé + plus

    de con!ance - prend du temps (au début) - plus de code (?) Avantages Inconvénients
  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
  13. Suite de Fibonacci class FibonacciTest extends \PHPUnit_Framework_TestCase { public function

    testFibonacci() { $fibonacci = new Fibonacci(); $this->assertEquals(0, $fibonacci->fib(0)); } } RED 13
  14. class Fibonacci { public function fib($value) { return 0; }

    } Fake it! Suite de Fibonacci GREEN 14
  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
  16. class Fibonacci { public function fib($value) { if ($value ===

    0) { return 0; } return 1; } } Suite de Fibonacci GREEN 16
  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
  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
  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
  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
  21. public function getValues() { return array( array(0, 0), array(1, 1),

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

    array(2, 1), array(3, 2), ); } RED Triangulation Suite de Fibonacci 19
  23. class Fibonacci { public function fib($value) { if ($value ===

    0) { return 0; } if ($value <= 2) { return 1; } return 2; } } GREEN Suite de Fibonacci 20
  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
  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
  26. METTRE EN PLACE LE TDD DANS VOS PROJETS SYMFONY2 23

  27. Créer une nouvelle page 24

  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
  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
  30. Créer une nouvelle page RED 27 <?php namespace Theodo\Bundle\ExpertBundle\Tests\Controller; use

    Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * ExpertControllerTest * * @author Benjamin Grandfond <benjaming@theodo.fr> * @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()); } }
  31. Créer une nouvelle page RED 27 <?php namespace Theodo\Bundle\ExpertBundle\Tests\Controller; use

    Symfony\Bundle\FrameworkBundle\Test\WebTestCase; /** * ExpertControllerTest * * @author Benjamin Grandfond <benjaming@theodo.fr> * @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
  32. Créer une nouvelle page 28 TIP

  33. RED Créer une nouvelle page 29

  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
  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 %} <ul> <li>Marek</li> <li>Pierre-Henri</li> </ul> {% endblock %} Créer une nouvelle page 31
  36. GREEN Créer une nouvelle page 32

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

    for expert in experts %} <li>{{ expert }}</li> {% endfor %} </ul> {% 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
  38. REFACTOR Créer une nouvelle page 34

  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
  40. Utilisation de !xtures 36

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

    outils à utiliser 37
  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
  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
  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' => '<firstName()>', 'lastName' => '<lastName()>', 'username' => '<userName()>' ), ) ); } Créer une nouvelle page REFACTOR 40
  45. REFACTOR Créer une nouvelle page private function getExperts() { return

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

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

    array( 'Theodo\Bundle\ExpertBundle\Entity\Expert' => array( 'expert{1..5}' => array( 'firstName' => '<firstName()>', 'lastName' => '<lastName()>', 'username' => '<userName()>' ), ) ); } formateurs Faker créé 5 experts
  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
  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
  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
  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
  52. REFACTOR Créer une nouvelle page 44

  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
  54. Réutiliser l’existant ... qui n’a pas de tests 46

  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
  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
  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
  58. {% extends '::base.html.twig' %} {% block body %} <ul> {%

    for expert in experts %} <li>{{ expert }}</li> {% endfor %} </ul> <form action="{{ path('theodoexpertbundle_expert_list') }}" method="post"> {{ form_errors(form) }} {{ form_widget(form) }} <button type="submit">Save</button> </form> {% endblock %} Réutiliser l’existant 48 Template
  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
  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
  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
  62. RED GREEN ou ? Tester l’existant 51

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

    formulaire
  64. 53

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

    code, testez les tests !
  66. {% extends '::base.html.twig' %} {% block body %} <ul> </ul>

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

    du formulaire
  68. {% extends '::base.html.twig' %} {% block body %} <ul> {%

    for expert in experts %} <li>{{ expert }}</li> {% endfor %} </ul> {% endblock %} Tester les tests 56 ✘ l’a!chage de la liste et du formulaire
  69. 57 Tester les tests ✘ l’a!chage de la liste et

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

    body %} <ul> {% for expert in experts %} <li>{{ expert }}</li> {% endfor %} </ul> <form action="{{ path('theodoexpertbundle_expert_list') }}" method="post"> {{ form_errors(form) }} {{ form_widget(form) }} <button type="submit">Save</button> </form> {% endblock %} ✘ l’a!chage de la liste et du formulaire
  71. 59 Tester les tests GREEN ✔ l’a!chage de la liste

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

  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
  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
  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
  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
  77. 64 Tester l’existant ✔ la sauvegarde d’un nouvel expert

  78. Encore une fois : Ne faites pas con!ance aux tests

    écrits après le code, testez les tests ! 65
  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
  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
  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é
  82. 69 Mock ?!

  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 • ...
  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
  85. RED 72 Refactoriser l’existant ✘ retourne le formulaire erroné

  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é
  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 !
  88. GREEN 74 Refactoriser l’existant ✔ retourne le formulaire erroné

  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
  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
  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
  92. RED 76 Refactoriser l’existant ✘ validité du formulaire ✘ retourne

    une redirection
  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
  94. GREEN 78 Refactoriser l’existant ✔ validité du formulaire ✔ retourne

    une redirection
  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); // ...
  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
  97. REFACTOR class ExpertFormHandlerTest extends \PHPUnit_Framework_TestCase { protected $handler; public function

    setUp() { $this->handler = new ExpertFormHandler(); } //... 80 Refactoriser l’existant
  98. REFACTOR public function getFormMock() { return $this ->getMockBuilder('Symfony\Component\Form\Test\FormInterface') ->disableOriginalConstructor() ->getMock();

    } 81 Refactoriser l’existant
  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); // ...
  100. REFACTOR 83 Refactoriser l’existant

  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
  102. RED 85 Refactoriser l’existant ✘ bind le formulaire avec la

    request
  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
  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
  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
  106. GREEN 88 Refactoriser l’existant ✔ bind le formulaire avec la

    request
  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); } }
  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
  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
  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
  111. RED 91 Refactoriser l’existant ✘ génère la route avec le

    router
  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
  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
  114. GREEN 94 Refactoriser l’existant ✔ génère la route avec le

    router
  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
  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
  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
  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
  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
  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
  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é
  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é
  123. RED 98 Refactoriser l’existant ✘ sauvegarder l’entité du formulaire

  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
  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
  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
  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
  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
  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
  130. 101 GREEN Refactoriser l’existant ✔ sauvegarder l’entité du formulaire

  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
  132. Maintenant on peut faire l’édition d’un expert en TDD !

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

    103
  134. Conclusion • Soyez patient • Persévérez • (Ré)introduisez progressivement des

    tests • Testez ce qui est primordial • Refactorisez, corrigez, créez en TDD
  135. Questions ?  benja-M-1  benjaM1  git@github.com: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