2. Unit testing
One of the main reasons for unit testing is
improvement of code quality.
Unit tests are indicators that instantly show all the
defects of object oriented code.
If code is hard to test – its quality is questionable.
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. Fat constructors
Objects that have a lot of behavior in
constructors are hard to test.
You’ve just created the object and it already
created other objects, made some global calls,
changed some global state, etc.
5. Fat constructors » Solution
Bad: mock all dependencies, create constructor
tests and test all scenarios of constructor.
Good: Move all behavior from constructors.
Leave only data initialization code.
6. Method Complexity
Unit tests test behavior scenarios. Unit testing
paradigm requires every scenario covered by
separate test.
Each flow control statement adds scenario to
method, so complex methods with many control
structures and protected calls require a lot of
tests and mocks/stubs.
7. Method Complexity » Solution
Bad: For protected calls – use reflection or
inheritance to test protected behavior in
isolation. Write test per each scenario.
Good: Extract behavior from complex methods
to separate objects that have small
dependencies and are easily testable.
Substitute conditions with polymorphism.
8. Poor OOD (God objects)
If an object has too many responsibilities there
is a big chance that it will have internal calls
between its public methods.
This creates problems for testing. Developer
has to mock whole object to stub internal public
calls, otherwise he will have test duplication.
9. Poor OOD (God objects) » Solution
Bad: Mock tested object and stub internal
public calls.
Good: Extract small objects that will have their
own responsibilities to avoid internal public
calls.
10. Law of Demeter Violations
When tested object receives some context
object that is used only to gain access to third
service object the Law of Demeter is violated.
To test such code one would have to stub
context object only to return service object. If
the chains of calls are long, testing becomes
problematic.
11. Law of Demeter Violations » Solution
Bad: Create mocks for context objects that will
return themselves on every call except
predefined stubbed calls.
Good: Refactor code to eliminate context
objects. Depend only on objects that are
required for delivering business goals of objects
under test.
12. Global State And Behavior
Global mutable state is a reason for most bugs. It
is unreliable for code that uses it.
Global state decreases code testability. Code that
uses global state can not be tested in isolation.
Developer must reproduce global environment of a
unit to test it.
Global behavior is a killer of testability. It can not
be mocked or stubbed for testing.
13. Global State And Behavior in Magento
• Global arrays
• Global variables
• Global factories
• Mix (state + behavior)
14. Global State » Arrays
Mage::registry, Mage::register and
Mage::unregister form an interface of
global dynamic service locator simply
wrapping mutable array with global
behavior that restricts access to array
but makes it harder to emulate in testing
environment
15. Global State » Objects And Variables
• Mage::app()
• Mage::getConfig()
• Mage::get/setIsdeveloperMode()
• Mage::getIsDownloader()
16. Global State » Factories
Global factories localize important part of application
behavior – object creation. They eliminate direct
dependencies in code. Which is good.
But instead of implicitly depending on created objects, code
that uses global factories starts to implicitly depend on
them.
Also global factories cannot be substituted in test
environments.
17. Global State » Factories » Examples
• Mage::getModel()
• Mage::getResourceModel()
• Mage::getControllerInstance()
18. Global State » Mix (Behavior + State)
These are dangerous in code and are hard to
substitute in tests:
• Mage::getSingleton()
• Mage::helper()
• Mage::getResourceHelper()
19. Global Behavior and State » Big deal
The big problem with global state and
behavior in Magento is it’s used everywhere.
All the dependencies of objects are pulled
from global state instead of being pushed
(injected) into these objects.
We cannot simply refactor our code to
eliminate global dependencies. We would
have to rewrite magento.
20. Global Behavior and State » Solution 1
Build “Magento Unit Testing Framework” on top
of PHPUnit, write testing-environment-special
Mage, make it “mockable” and test our code “in
isolation”.
This is an absolutely viable solution that will let
us unit test our code in isolation avoiding
massive refactoring.
21. The problem
The problem with this solution is the same as
with bad solutions described in previous
sections: It fights the symptom (code non-
testability), not the disease (global state).
And mutable global state and behavior is the
reason of hard to debug type of bugs and
encourages bad practices.
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. 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 code
smells will become visible by only looking at
method signatures. LSP violations will be noted by
interpreter.
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 too
much.
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 too
much.
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 refactor
the object he tests – not all the code that uses
it. This can be a stage of transforming magento
code to prepare it for DiC.