Lightening Talk I gave at Inaka in May 2014.
This is sort of the continuation of my previous iOS TDD talk.
Since TDD and DI go quite hand in hand and they are both extend concepts to learn in one shot, I had to prepare a completely separated talk for spreading to my fellows the DI knowledge I had acquired.
2. I’m going to talk about…
• DI concept
• DI relevant concepts
• How to apply DI
• TDD+DI example (in iOS)
• TDD+DI real case (in iOS)
• DI Pros & Cons
• References
4. What is Dependency Injection?
Dependency Injection (DI) is a software design pattern
that implements inversion of control (IoC) and
allows a program design to follow the
dependency inversion principle.
- Wikipedia
5. What is Dependency Injection?
Dependency Injection (DI) is a set of software design
principles and patterns that enables us to develop
loosely coupled code.
- Mark Seemann (Dependency Injection in .NET)
6. What is Dependency Injection?
The basic concept is that we separate
dependencies from classes that uses them.
But… What is a dependency?
A dependency is an object that your class under test
interacts with.
7. What is Dependency Injection?
CollaboratorSUT
DependencyClass
Interaction
A dependency is a collaborator, is someone that your
class under test needs to use for some reason.
8. What is Dependency Injection?
The problem is that we use to create dependencies from within the SUT
The base of DI is that we should create dependencies from
somewhere else, not within our SUT
CollaboratorSUT
DependencyClass
Instantiation
Interaction
CollaboratorSUT
DependencyClass
Interaction Instantiation
Somewhere
else
Another class
9. What is Dependency Injection?
The idea of DI is that somehow we will inject those dependencies into
our SUT…
Collaborator
Dependency
SUT
Class
Interaction
Instantiation
Somewhere
else
Another class
Injection
This separates construction from behavior
10. What is Dependency Injection?
Let’s see a real world example (an Inaka world example)…
11. What is Dependency Injection?
Let’s see a real world example (an Inaka world example)…
Say we are testing Nacho, say we want to see whether he has a good
working performance, i.e. whether he is producing as we expect.
Guy under test
12. What is Dependency Injection?
Our tester will be Demian (couldn’t be other way…)
Evaluates
The tester
The guy
under test
13. What is Dependency Injection?
But, let’s say Nacho needs Gera’s help to do his work…
Evaluates Needs
The tester
The guy
under test
The
dependency
So, Nacho is depending upon Gera to have his job done.
There is some task that Nacho needs from Gera to be done to complete
his work.
14. What is Dependency Injection?
Even though Gera is a hard-working guy,
he is human, so it will take some time and
effort to complete the work he has to do for
Nacho.
But, wait… We are not testing Gera’s
performance, but Nacho’s.
In other words, as the guy under test is
Nacho, we should not depend upon Gera’s
work to see how Nacho works.
So, what to do…?
15. What is Dependency Injection?
Well, Demian (the tester) can make up some sort of fake Gera, one
that will have everything there for Nacho as soon as he needs it.
Imagine this fake Gera as some sort of terminator that will have
everything Nacho needs immediately and perfectly done for him.
Creates
The tester
The
dependency
fake Gera
16. What is Dependency Injection?
Creates
The tester
The
dependency
Passes
it to
The guy
under test
Nacho needs a Gera to help him, and it’s
Demian who’s telling him:
“Hey, take this Gera and use this one”
Interacts
with
fake Gera
17. What is Dependency Injection?
At this point, we see that it’s not up to Nacho to choose WHAT
Gera to use, all he needs to know is that he’s going to use some sort
of Gera to help him.
It’s another guy who is choosing WHAT Gera to use, that guy is
Demian.
This way we assure that we are testing Nacho, no matter what Gera
he uses, as long as he uses one.
18. Collaborator
What is Dependency Injection?
Let’s see the equivallences of this example and the OO world…
Creates
The tester
The
dependency
Passes
it to
The guy
under test
Instantiates
The tester
The
dependency
Injects
The system
under test
Test class
SUT
Note that “passing the dependency to” is referred to as “injecting” the dependency.
Interacts
with
Interacts
with
fake Gera
19. What is Dependency Injection?
This way of working leads us to develop loosely-coupled designs
Collaborator
Creation
Injection
Class
Class
Interaction
20. What is Dependency Injection?
This way of working leads us to develop loosely-coupled designs
WITHOUT DI WITH DI
21. What is Dependency Injection for?
DI’s overall purpose is code maintainability.
By writing maintainable code,
we will spend less time finding and fixing bugs.
One of many ways to write maintainable code is through
loose coupling.
23. S
O
L
I
D
ingle Responsibility
pen / Closed
iskov Substitution
nterface Segregation
ependency Inversion
A class should have only one single responsibility.
Classes should be open for extension, but closed for modification.
Objects in a program should be replaceable with other objects of the
same interface without breaking either client or implementation.
Many client-specific interfaces are better than one general-purpose
interface.
Depend upon abstractions. Do not depend upon concretions.
25. Unit tests provide rapid feedback on the state of an
application.
However, it’s only possible to write unit tests when the
unit in question can be properly isolated from its
dependencies.
TDD is the safest way to ensure testability.
(a software is considered testable
when it’s susceptible to unit testing)
26. That’s the point in which TDD is related to DI
We use DI to isolate classes from their dependencies
TDD leverages unit tests, which need our classes to be
isolated from their dependencies
27. Stable Dependencies -vs- Volatile
Dependencies
You expect that new versions
won’t contain breaking
changes
Class already exists
Types in question contain
deterministic algorythms
You never expect to have to
replace the class with another
Example: UITextField
The class introduces a
requirement to setup and
configure runtime envionment
The class contains
nondeterministic behavior
Important in unit tests: All tests
should be deterministic
Example: AFNetworking
The class doesn’t yet exist,
but it’s still in development
28. Volatile Dependencies are the focal point of DI
When we have a volatile dependency, we must
introduce a seam in our class.
A seam is sort of a gate,
from which we are going to relate a class with its dependency.
We can see seam as the separation between the class and its dependency.
29. Seams
Everywhere we decide to program against an interface instead of a
concrete type, we introduce a seam.
A seam is a place where an application is assembled from
its constituent parts.
This is similar to the way a piece of clothing is sewn together at its
seams.
A seam is also a place where we can disassemble the application
and work with their modules in isolation.
30. We won’t lose the control of the dependency, though.
We’ll just move it to another place.
This is an application of the Single Responsibility Principle,
because our classes would only deal with their given area
of responsibility.
This means we will make the class give up control of the
dependency (hence, its lifetime).
Seams
41. Subclasses can override this method and provide whatever manager they want.
extract
* Observe that [AFHTTPRequestOperationManager manager] has become our default manager.
Extract and override
43. … or whatever you want
Extract and override
Subclass
44. Here (in a subclass) you can provide whatever manager you want.
This allows testing classes to replace real dependency objects with fakes.
In this case, you must use your testable version of the class (i.e. your subclass) to test.
… or whatever you want
Extract and override
Subclass
47. This allows us to inject whatever instance we need at any moment we
need to use the method.
Usually, we will inject a fake object when testing the method…
Method injection
We inject the dependency from the outside
81. Let’s add a trick to default initialize our dependency
in our View Controller…
Thus, other classes that need to use CounterViewController don’t have to remember what
dependencies to initialize (even though they optionally can) and unnecessarily mess the
code up everywhere.
In CounterViewController.m
default initializer
90. Currently, our
test method is not ensuring that the “did increment” call
should be done right after incrementing, and not before.
Having said that, we will know that this test will pass,
because it’s only testing that the “did delegate” call is
being done, no matter when, as long as we call
[counter increment];
91. This is giving us a false sense of correctness.
That’s one of the drawbacks of TDD that we saw in my
previous talk about TDD.
Currently, our
test method is not ensuring that the “did increment” call
should be done right after incrementing, and not before.
92. So, we will need to modify this test to make sure the
“did increment” delegate call is being fired once the
integer counter property has been increased.
Currently, our
test method is not ensuring that the “did increment” call
should be done right after incrementing, and not before.
106. DailyReader
DailyReader is an iOS app that fetches all the messages of the
current day from Inaka’s main room and displays them on the
screen through a grouped table, categorizing them into three
different groups:
‣ “dailies”
‣ “not so descriptive dailies”
‣ “other messages”
It was entirely developed by applying TDD and DI.
https://github.com/inaka/daily-reader
108. This is what DailyReader could have looked like if we had not applied TDD+DI
DRMessages
ViewController
DRManager
AFHTTP
OperationManager
DRMessage
via delegate
via
blocks
Model Classes
View/Controller
Classes
External Classes
References
asks
for
messages
needs
returns array of messages
returns
responsesasks for
JSON
needs
109. This is what DailyReader could have looked like if we had not applied TDD+DI
DRMessages
ViewController
DRManager
AFHTTP
OperationManager
DRMessage
via delegate
via
blocks
Model Classes
View/Controller
Classes
External Classes
References
asks
for
messages
needs
returns array of messages
returns
responsesasks for
JSON
• Fewer classes
• Classes are tightly coupled
• Responsibilites are not clear
• Code is less extensible
• Code is less testable
• Code is less maintainable
What do we see here?
needs
110. This is what DailyReader could have looked like if we had not applied TDD+DI
DRMessages
ViewController
AFHTTP
OperationManager
DRMessage
via delegate
via
blocks
Model Classes
View/Controller
Classes
External Classes
References
asks
for
messages
needs
returns array of messages
returns
responsesasks for
JSON
needs
DRManager
would be a God Class
with these responsibilites:
• Communicating to
networking classes
• Configuring request URLs
• Parsing responses into
model objects
• Creating model objects
It definitely breaks the
Single
Responsibility
Principle
such a
code smell
112. When we applied TDD+DI, we spent more time and effort on the developing
process, but on the other hand, we’ve been rewarded with these great things:
✓ Flexible architecture
✓ Extensible code
✓ Maintainable code
… and, the best one…
✓ We now have tests that will catch us (or any developer who
has to modify code) in case we make mistakes when
refactoring, bugfixing, or adding new features.
Benefits of applying TDD + DI
… all these things will lead us to spend much less time and effort on future
refactorings / bug fixings processes.
113. Remember this?
Team progress and output measured with and without tests
- Roy Osherove, The Art Of Unit Testing
“That’s why it’s important to emphasize that, although unit testing can increase the amount of
time it takes to implement a feature, the time balances out over the product’s release cycle because
of increased quality and maintainability.”
114. There are also more benefits of applying dependency injection:
✓ Classes are easier to unit-test in isolation, leveraging fake objects.
✓ We can externalize system’s configuration details into
configuration files, allowing the system to be reconfigured without
recompilation.
✓ Separated configurations can be written for different situations that
require different implementations of components.
✓ DI allows concurrent or independient development.
Even more benefits of applying TDD + DI !
115. But… Be careful: There are some downsides when applying DI…
๏ Code is more difficult to trace (read).
๏ We will usually require more lines of code to
accomplish the same behavior legacy code would.
๏ It adds much more time and effort to the process of
code development.
Because DI separates behavior from construction