Magento code testability: Problems and Solutions

1,442 views

Published on

Published in: Technology, Education
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,442
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
7
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Magento code testability: Problems and Solutions

  1. 1. Magento code testability:Problems and Solutions
  2. 2. Unit testingOne of the main reasons for unit testing isimprovement of code quality.Unit tests are indicators that instantly show all thedefects of object oriented code.If code is hard to test – its quality is questionable.
  3. 3. Code flaws in magento detected by unit tests• Fat constructors• Method complexity• Poor OOD (God objects)• Law of Demeter violations• Global State and Behavior usage• …
  4. 4. Fat constructorsObjects that have a lot of behavior inconstructors are hard to test.You’ve just created the object and it alreadycreated other objects, made some global calls,changed some global state, etc.
  5. 5. Fat constructors » SolutionBad: mock all dependencies, create constructortests and test all scenarios of constructor.Good: Move all behavior from constructors.Leave only data initialization code.
  6. 6. Method ComplexityUnit tests test behavior scenarios. Unit testingparadigm requires every scenario covered byseparate test.Each flow control statement adds scenario tomethod, so complex methods with many controlstructures and protected calls require a lot oftests and mocks/stubs.
  7. 7. Method Complexity » SolutionBad: For protected calls – use reflection orinheritance to test protected behavior inisolation. Write test per each scenario.Good: Extract behavior from complex methodsto separate objects that have smalldependencies and are easily testable.Substitute conditions with polymorphism.
  8. 8. Poor OOD (God objects)If an object has too many responsibilities thereis a big chance that it will have internal callsbetween its public methods.This creates problems for testing. Developerhas to mock whole object to stub internal publiccalls, otherwise he will have test duplication.
  9. 9. Poor OOD (God objects) » SolutionBad: Mock tested object and stub internalpublic calls.Good: Extract small objects that will have theirown responsibilities to avoid internal publiccalls.
  10. 10. Law of Demeter ViolationsWhen tested object receives some contextobject that is used only to gain access to thirdservice object the Law of Demeter is violated.To test such code one would have to stubcontext object only to return service object. Ifthe chains of calls are long, testing becomesproblematic.
  11. 11. Law of Demeter Violations » SolutionBad: Create mocks for context objects that willreturn themselves on every call exceptpredefined stubbed calls.Good: Refactor code to eliminate contextobjects. Depend only on objects that arerequired for delivering business goals of objectsunder test.
  12. 12. Global State And BehaviorGlobal mutable state is a reason for most bugs. Itis unreliable for code that uses it.Global state decreases code testability. Code thatuses global state can not be tested in isolation.Developer must reproduce global environment of aunit to test it.Global behavior is a killer of testability. It can notbe mocked or stubbed for testing.
  13. 13. Global State And Behavior in Magento• Global arrays• Global variables• Global factories• Mix (state + behavior)
  14. 14. Global State » ArraysMage::registry, Mage::register andMage::unregister form an interface ofglobal dynamic service locator simplywrapping mutable array with globalbehavior that restricts access to arraybut makes it harder to emulate in testingenvironment
  15. 15. Global State » Objects And Variables• Mage::app()• Mage::getConfig()• Mage::get/setIsdeveloperMode()• Mage::getIsDownloader()
  16. 16. Global State » FactoriesGlobal factories localize important part of applicationbehavior – object creation. They eliminate directdependencies in code. Which is good.But instead of implicitly depending on created objects, codethat uses global factories starts to implicitly depend onthem.Also global factories cannot be substituted in testenvironments.
  17. 17. Global State » Factories » Examples• Mage::getModel()• Mage::getResourceModel()• Mage::getControllerInstance()
  18. 18. Global State » Mix (Behavior + State)These are dangerous in code and are hard tosubstitute in tests:• Mage::getSingleton()• Mage::helper()• Mage::getResourceHelper()
  19. 19. Global Behavior and State » Big dealThe big problem with global state andbehavior in Magento is it’s used everywhere.All the dependencies of objects are pulledfrom global state instead of being pushed(injected) into these objects.We cannot simply refactor our code toeliminate global dependencies. We wouldhave to rewrite magento.
  20. 20. Global Behavior and State » Solution 1Build “Magento Unit Testing Framework” on topof PHPUnit, write testing-environment-specialMage, make it “mockable” and test our code “inisolation”.This is an absolutely viable solution that will letus unit test our code in isolation avoidingmassive refactoring.
  21. 21. The problemThe problem with this solution is the same aswith bad solutions described in previoussections: It fights the symptom (code non-testability), not the disease (global state).And mutable global state and behavior is thereason of hard to debug type of bugs andencourages bad practices.
  22. 22. The problem » Bad practices• Liar interfaces• Law of Demeter violations• Deep code dependencies• Liskov substitution principle violations• Separation of concerns violations• Data envy• ….
  23. 23. Global State » Solution• Declare all object dependencies explicitly as entries in constructor argument array, and all the method-specific arguments as method’s parameters instead of pulling them from global state. Use object managers instead of global arrays when needed.It will make our interfaces honest. And most codesmells will become visible by only looking atmethod signatures. LSP violations will be noted byinterpreter.
  24. 24. Global Behavior » Solution• If an object must create other objects then it must declare dependency on object factory, that will be injected into the object.It will instantly show objects that create toomuch.
  25. 25. Global Behavior » Solution• If an object must create other objects then it must declare dependency on object factory, that will be injected into the object.It will instantly show objects that create toomuch.
  26. 26. Global usage of Global » Solution• All the constructors will check presence of dependencies in arguments, if an argument is not present – it is taken from Global.It will let us avoid massive refactorings.Because developer will only have to refactorthe object he tests – not all the code that usesit. This can be a stage of transforming magentocode to prepare it for DiC.
  27. 27. Sources• http://misko.hevery.com/• http://martinfowler.com/articles/injection.html• Test-Driven Development by Example. Kent Beck
  28. 28. Author Anton Kril Kyiv Dev Folks team akril@ebay.com

×