Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Mage Titans 2016 - Object(ivly) Thinking

349 views

Published on

We know we should be testing our code but what are the benefits of testing? Assurances that we can rapidly make changes to the code and ensure functionality is still kept intact.

Well, what if there was more than just code security that practising testing could give us.

I believe that by thinking about Objects and going back to the fundamentals of OOP ( Object Orientated Programming ) and thinking about how objects can **communicate** with each other we can design better applications adhering to **SRP** ( Single responsibility Principle ), **SOLID** and **DRY** and still retain a well tested and more verbosely documented system.

By exploring concepts from OOP, DDD, BDD and TDD I will show that we can extract business domain code and encapsulate this into meaningful objects that are more testable, maintainable and showcase best OOP practices.

Published in: Technology
  • Be the first to comment

Mage Titans 2016 - Object(ivly) Thinking

  1. 1. Hello World @Jcowie Magento ECG Mage-Casts Github: Jamescowie
  2. 2. Background on OOP Insight into my thinking Insight into my workflow A fun 30 mins Objectively
  3. 3. Object Oriented Programming
  4. 4. Messaging
  5. 5. Principles of GOOD software
  6. 6. SOLID Uncle Bob Martin
  7. 7. Single Responsibility Principle A class should have one and only one reason to change, meaning that a class should have only one job.
  8. 8. Open Closed Objects or entities should be open for extension, but closed for modification.
  9. 9. /** * @api */ interface ProductRepositoryInterface { …… }
  10. 10. <preference for="MagentoCatalogApiProductRepositoryInterface" type="MagentoCatalogModelProductRepository” />
  11. 11. Interface Segregation Principle A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.
  12. 12. Dependency Inversion Principle Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.
  13. 13. /** * @api */ interface ProductRepositoryInterface { …… }
  14. 14. <preference for="MagentoCatalogApiProductRepositoryInterface" type="MagentoCatalogModelProductRepository” />
  15. 15. I
  16. 16. A framework is just one of the tools to help you developer “Better” and “faster” - Symfony documentation
  17. 17. Convenient code Maintainable code
  18. 18. Convenient code Maintainable code Coupled code Mixed responsibility Bound to framework Harder to test
  19. 19. Convenient code Maintainable code Coupled code Mixed responsibility Bound to framework Harder to test Decoupled code Separated domain Framework agnostic Easier to test
  20. 20. public function execute() { if ($this->_request->getParam(MagentoFrameworkAppActionInterface::PARAM_NAME_URL_ENCODED)) { return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl()); } $category = $this->_initCategory(); if ($category) { $this->layerResolver->create(Resolver::CATALOG_LAYER_CATEGORY); $settings = $this->_catalogDesign->getDesignSettings($category); // apply custom design if ($settings->getCustomDesign()) { $this->_catalogDesign->applyCustomDesign($settings->getCustomDesign()); } $this->_catalogSession->setLastViewedCategoryId($category->getId()); ….. } Magento can be convenient
  21. 21. class Index extends MagentoFrameworkAppActionAction
  22. 22. /** @var MagentoFrameworkViewResultPageFactory */ protected $resultPageFactory; public function __construct( MagentoFrameworkAppActionContext $context, MagentoFrameworkViewResultPageFactory $resultPageFactory ) { $this->resultPageFactory = $resultPageFactory; parent::__construct($context); }
  23. 23. Large number of dependencies MagentoFrameworkAppActionContext $context, MagentoFrameworkViewResultPageFactory $resultPageFactory MagentoFrameworkRegistry $coreRegistry, MagentoStoreModelStoreManagerInterface $storeManager, CategoryRepositoryInterface $categoryRepository
  24. 24. Unit testing becomes hard
  25. 25. The “convenient” controller requires more Integration Tests
  26. 26. Potential code smell
  27. 27. Lets see and Example
  28. 28. protected function configure() { $this->setName('generate:showcaseproducefeed'); $this->setDescription('Generate a produce feed of showcase products in json'); parent::configure(); }
  29. 29. protected function execute(InputInterface $input, OutputInterface $output) { $product = $this->products->get('24-MB01'); $writer = $this->filesystem->getDirectoryWrite('var'); $file = $writer->openFile('showcase.json', 'w'); try { $file->lock(); try { $file->write(json_encode(['product_name' => $product->getName(), 'product_price' => $product->getPrice() ])); } finally { $file->unlock(); } } finally { $file->close(); } $output->writeln("Feed Generated"); }
  30. 30. Can we add XML feed generation
  31. 31. protected function execute(InputInterface $input, OutputInterface $output) { $product = $this->products->get('24-MB01'); $writer = $this->filesystem->getDirectoryWrite('var'); $file = $writer->openFile('showcase.json', 'w'); $xmlFile = $writer->openFile('showcase.xml', 'w'); try { $file->lock(); try { $file->write(json_encode(['product_name' => $product->getName(), 'product_price' => $product->getPrice() ])); $showcaseXML = new SimpleXMLElement("<showcase></showcase>"); $showcaseXML->addAttribute('showcase-products', 'today'); $showcaseAttributes = $showcaseXML->addChild($product->getName()); $showcaseAttributes->addAttribute('price', $product->getPrice()); $xmlFile->write($showcaseXML->asXML()); } finally { $file->unlock(); }… $output->writeln("Feed Generated"); }
  32. 32. Our class had reason to change
  33. 33. How can we test this ?
  34. 34. public function __construct( MagentoCatalogApiProductRepositoryInterface $productRepository, MagentoFrameworkAppState $state, MagentoFrameworkFilesystem $filesystem ) { $state->setAreaCode('frontend'); $this->products = $productRepository; $this->filesystem = $filesystem; parent::__construct(); } $showcaseXML = new SimpleXMLElement("<showcase></showcase>");
  35. 35. Scenario: Products are presented in JSON file Given The generate command exists When I run the generate command Then I should see a JSON file created
  36. 36. use BehatBehatContextContext; use SymfonyComponentProcessProcess; use SymfonyComponentFilesystemFilesystem;
  37. 37. class FeatureContext implements Context { private $output; private $filesystem; public function __construct() { $this->filesystem = new Filesystem(); } }
  38. 38. /** * @Given The generate command exists */ public function theGenerateCommandExists() { return true; }
  39. 39. /** * @When I run the generate command */ public function iRunTheGenerateCommand() { $process = new Process("php " . getcwd() . "/../../../generate:showcaseproducefeed"); $process->run(); $this->output = $process->getOutput(); }
  40. 40. /** * @Then I should see a JSON file created */ public function iShouldSeeAJsonFileCreated() { expect(file_exists(__dir__ . '/../../../../../../var/showcase.json'))->toBe(true); }
  41. 41. Tests Pass
  42. 42. Scenario: Products are presented in XML file Given The generate command exists When I run the generate command Then I should see a XML file created
  43. 43. Tests Fail
  44. 44. We could add this into the command buuuut.
  45. 45. Open Closed Principle
  46. 46. <?php namespace TitansShowcaseApi; interface BuilderInterface { public function build($data); }
  47. 47. Create a scenario for extraction
  48. 48. Scenario: Class file does return json When I call the JSONBuilder class Then I should be returned a json string
  49. 49. public function __construct() { $this->builder = new TitansShowcaseCommandsBuildersJsonBuilder(); }
  50. 50. /** * @When I call the JSONBuilder class */ public function iCallTheJsonbuilderClass() { $this->output = $this->builder->build(['name' => 'james']); } /** * @Then I should be returned a json string */ public function iShouldBeReturnedAJsonString() { expect($this->output)->toBe(json_encode(['name' => 'james'])); }
  51. 51. <?php namespace TitansShowcaseCommandsBuilders; class JsonBuilder implements TitansShowcaseApiBuilderInterface { public function build($data) { return json_encode($data); } }
  52. 52. <?php namespace TitansShowcaseCommandsBuilders; class JsonBuilder implements TitansShowcaseApiBuilderInterface { public function build($data) { return json_encode($data); } }
  53. 53. <?php namespace TitansShowcaseCommandsBuilders; class XmlBuilder implements TitansShowcaseApiBuilderInterface { public function build($data) { return …. } }
  54. 54. public function __construct( TitansShowcaseCommandsBuildersJsonBuilder $jsonBuilder ) { $this->jsonBuilder = $jsonBuilder; parent::__construct(); }
  55. 55. protected function execute(InputInterface $input, OutputInterface $output) { $product = $this->products->get('24-MB01'); $this->writer->write( 'showcase.json', $this->jsonBuilder->build( ['product_name' => $product->getName()] ) ); }
  56. 56. Command Class Builder Class Sends Message
  57. 57. Command Class Responsibility for handling the command
  58. 58. JSON Builder Class Responsibility Building JSON based on a message Value Object.
  59. 59. Command Class Builder Class Integration Tests Framework code Unit Tests Library code
  60. 60. What are the benefits ? We now pass messages between objects We are testing to some level using Behat We have clear Objects We use Value Objects

×