Behat Layered Cake

Behat Layered Cake

Speed up Behat tests with a clever use of the layered architecture.

2bd48651cd01e0ca2e0a255a63da77aa?s=128

Marek Matulka

December 05, 2014
Tweet

Transcript

  1. 3.
  2. 4.

    A project $ bin/behat -fprogress ...................................................................... 70 ...................................................................... 140 ......................................................................

    210 ...................................................................... 280 ........................................ 63 scenarios (63 passed) 320 steps (320 passed) 10m53.01s (61.38Mb)
  3. 13.

    A project $ bin/behat -fprogress ...................................................................... 70 ...................................................................... 140 ......................................................................

    210 ...................................................................... 280 ........................................ 63 scenarios (63 passed) 320 steps (320 passed) 1m15.72 (52.94Mb)
  4. 15.
  5. 18.

    What if I had 3000 steps? 300 steps took over

    10 minutes 3000 steps will take over 100 minutes! (inside guest VM)
  6. 19.

    What if I had 3000 steps? 300 steps took 1.5

    minutes 3000 steps will take 15 minutes! (on host)
  7. 21.
  8. 24.

    Dependency Inversion “High level modules should not depend on lower

    level implementation” – Good old Uncle Bob
  9. 28.

    Hexagonal Architecture UI Adapter REST Adapter Data Storage Adapter Domain

    Internal client port external client port persistence layer port Data Provider Adapter data provider port
  10. 29.

    Hexagonal Architecture UI Adapter REST Adapter Test Adapter Test Adapter

    Data Storage Adapter Test Adapter Domain External Data Adapter Test Adapter
  11. 32.

    Describe Communication interface ConferenceRepository { /** * @param Learner $learner

    * @return Conference[] */ public function findByLearner(Learner $learner); /** * @param string $conferenceId * @return Conference */ public function findOneById($conferenceId); /** * @param Conference $conference */ public function save(Conference $conference); }
  12. 33.

    Your controller class RegistrationController extends Controller { public function registerAction($id)

    { $conference = $this->getDoctrine() ->getRepository(‘AcmeConferenceBundle:Conference’) ->find($id); if ($this->get(‘acme_conference.enrolment.service’) ->enrol($conference, $this->getUser())) { $this->get(‘acme_conference.notification.service’) ->notify($this->getUser(), ‘success’); $this->redirect(‘registration_successful’) } $this->redirect(‘registration_failed’); } }
  13. 34.

    Spec Controller to use Interface class RegistrationController { private $repository;

    public function __construct(ConferenceRepository $repository) { $this->repository = $repository; } public function registerAction($id) { $conference = $this->repository->findOneById($id); ... } }
  14. 35.

    Implement your repository namespace Acme\DoctrineAdapter\Repository; class DoctrineConferenceRepository extends EntityRepository implements

    ConferenceRepository { ... public function findOneById($id) { return parent::find($id); } public function save(Conference $conference) { $this->getEntityManager()->persist($conference); $this->getEntityManager()->flush(); } }
  15. 36.

    Register repository as a Service! // Acme\DoctrineAdapter\Resources\mapping\Learning.Conference.orm.xml <entity name="Acme\Learning\Conference" repository-class="Acme\DoctrineAdapter\DoctrineConferenceRepository"

    table="conferences"> // Acme\Application\LearningBundle\Resources\config\services.xml <service id="rechannel.brand.repository" class="Doctrine\ORM\EntityManager" factory-service="doctrine.orm.entity_manager" factory-method="getRepository"> <argument>Acme\Learning\Conference</argument> </service>
  16. 38.

    Implement your repository namespace Acme\TestInfrastructure\Repository; class FilesystemConferenceRepository extends FilesystemCollection implements

    ConferenceRepository { public function findOneById($id) { return parent::load($id); } public function save(Conference $conference) { parent::add($conference, $conference->getId()); } }
  17. 39.

    Register repository as a Service! // Acme\Application\LearningBundle\Resources\config\filesystem_services.xml <service id="acme.conference.repository" alias="acme.brand.repository.filesystem"

    /> <service id="acme.conference.repository.filesystem" class="Acme\TestInfrastructure\Repository\FilesystemConferenceRepository"> </service>
  18. 42.

    Configure dependency injection class LearningExtension extends Extension { public function

    load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $this->processConfiguration($configuration, $configs); $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); if ($container->getParameter('acme.data_source') === 'filesystem') { $loader->load('filesystem_repositories.xml'); } } }
  19. 44.
  20. 46.
  21. 49.

    Hexagonal Architecture UI Adapter REST Adapter Test Adapter Test Adapter

    Data Storage Adapter Test Adapter Domain External Data Adapter Test Adapter
  22. 52.