2. Why you should invest time to write testable code
Testable code is easy to
maintain on the long run
Easy to test code makes the maintenance
straightforward for developers and cheap for
the stakeholders
Coding with testability in mind will drive the
code towards a flexible design, that allows
safe refactoring
2
You will write tests also
after the main release
Most projects deliver software first,
without a full test coverage
Unit tests are added also later, during
consolidation phases, every time you fix a
bug, or you implement a change request
Most likely other developers will also
extend and test your code
3. I am Marian Wamsiedel
I work as a java developer
mainly in java enterprise
projects
You can find me at
marian.wamsiedel@gmail.com
3HELLO!
4. What makes your code easy to test
Loose
coupling No static
calls
4
Logic free
constructors
Compact
data
creation
Clear intent
5. What makes your code easy to test
Loose
coupling No static
calls
5
Logic free
constructors
Compact
data
creation
Clear intent
7. Loose coupling
Replace creation of new objects with
dependency injection
Inject all collaborators in the constructor
If forced by the api, create fake constructors
visible from the unit tests
Handle the bloated constructors nicely
7
8. Loose coupling, dependency injection instead of objects creation
Each unit test will also create a real object if the
productive code instantiates a collaborator
The object creation is a potential slow operation
A created collaborator can not be faked (mocked,
or stubbed) in the test
8
9. Loose coupling, dependency injection instead of objects creation
In order to simulate the collaborators behavior, the
tests are forced to fake second/third level
collaborators, or to set up values in a database
The system dependencies are not transparent.
They are hidden in the implementation, not
declared in the public API
Replace each new call with dependency injection
9
10. Loose coupling, dependency injection instead of objects creation
The simple knowledge of second level
collaborators disqualifies the test as unit test
If the productive code instantiates a logic class,
like a calculator, the tests will check the behavior
of both classes, mixing the concepts
Changes in the collaborator's behavior can break
also the main class tests, leading to brittle tests
10
11. Loose coupling, dependency injection instead of objects creation
Example instantiated collaborator
public int averageCustomersAge(){
return new CustomerRepository().customers().stream()
.mapToInt(Customer::age)
.average().orElse(0d);}
The unit test should now fake the result of the
call: new CustomerRepository().customers(),
which requires know how about customer
repository collaborators
11
12. Loose coupling, dependency injection instead of objects creation
Example injected collaborator
class CustomerLogic {
private final CustomerRepository repository;
CustomerLogic(final CustomerRepository customerRepository){
this.repository = customerRepository;}
int averageCustomersAge(){
return repository.customers().stream()
.mapToInt(Customer::age)
.average().orElse(0d);}
}
12
13. Loose coupling, dependency injection instead of objects creation
Example injected collaborator, test
@Test
public void shouldCalculateAverageCorrectly(){
when(repository.customers()).thenReturn(
asList(
Customer.withAge(20),
Customer.withAge(30))
);
final CustomerLogic underTest = new CustomerLogic(repository);
assertThat(
underTest.averageCustomersAge(),
is(25d));
}
13
14. Loose coupling
Replace creation of new objects with
dependency injection
Inject all collaborators in the constructor
If forced by the api, create fake constructors
visible from the unit tests
Handle the bloated constructors nicely
14
15. Loose coupling, inject collaborators in constructor
The dependencies are visible in the class API
It is really easy to write unit tests. When you
instantiate the component under test, you just call
the constructor, passing mocks, or test doubles as
arguments
You can design immutable classes
15
16. Loose coupling, dependency injection instead of objects creation
It indicates possible design flaws: is your class
still cohesive and follows the single responsibility
principle, if it has way too many collaborators?
Avoid the field injection, even if your dependency
injection system allows it
16
17. Loose coupling
Replace creation of new objects with
dependency injection
Inject all collaborators in the constructor
If forced by the api, create fake constructors
visible from the unit tests
Handle the bloated constructors nicely
17
18. Loose coupling, fake test constructors
If the API forces a constructor signature that does
not allow the injection of the collaborators, do not
unit test this main constructor
Create a package-private constructor, that
accepts all collaborators as parameters
Unit test this package-private constructor
18
19. Loose coupling, fake test constructors
Example, difficult to test version
// The signature is forced by the API, cannot be changed
public MessageCenter(final String id){
super(id);
this.notificationService = new NotificationService();
this.mailService = new MailService();
}
This constructor cannot be unit tested in a
simple, clean way
19
20. Loose coupling, fake test constructors
Example, the easy to test version
// The signature forced by the API, cannot be changed
public MessageCenter(final String id){
this (id, new NotificationService(), new MailService());
}
// You can easy test this constructor
MessageCenter(final String id,
final NotificationService notificationService,
final MailService mailService) {...}
20
21. Loose coupling
Replace creation of new objects with
dependency injection
Inject all collaborators in the constructor
If forced by the api, create fake constructors
visible from the unit tests
Handle the bloated constructors nicely
21
22. Loose coupling, handle bloated constructor
Injecting all the dependencies in the constructor
can lead to a long parameter list
A long parameter list can signalize lack of
cohesion and therefore you should consider
refactoring the class in order to split the concerns
22
23. Loose coupling, handle bloated constructor
If refactoring is not required, search for
parameters that belong together:
Group the components into a new class. Use a
single instance of the class as constructor
argument
Use composition. Inject one object into another
and use a single instance of the main class as
constructor argument
23
24. What makes your code easy to test
Loose
coupling No static
calls
24
Logic free
constructors
Compact
data
creation
Clear intent
26. Design logic free constructors
The code in the constructor is run by each test. A
hard working constructor makes your tests slow
Avoid any object creation in the constructor, like
connections, entity managers, or providers
Any new call in constructor could trigger a
pyramid of calls, that will make your tests slow
and brittle
26
27. Design logic free constructors
The constructors should ideally just initialize
instance variables with constructor arguments
values
The usage of init() blocks, post construct
blocks, or static code initializers is cheating and
we know it
27
28. What makes your code easy to test
Loose
coupling No static
calls
28
Logic free
constructors
Compact
data
creation
Clear intent
29. 29
The instance method calls are easy to fake
Use them as long as possible, avoid static calls
30. Static calls
Collaborators are not visible in your API
Collaborators behavior can change without
notice
Static calls can force you to register transitive
dependencies
Singletons are dependencies
30
31. No static calls, collaborators not declared in API
The collaborators that receive the static call are
not declared transparently, in the public API
The static calls are like secret communication
channels to unknown entities
Replacing the static calls with instance calls on
injected collaborators make the API clear
31
32. Static calls
Collaborators are not visible in your API
Collaborators behavior can change without
notice
Static calls can force you to register transitive
dependencies
Singletons are dependencies
32
33. No static calls, collaborator's behavior changes
Since not all the mocking frameworks support
static calls, you might end up running real static
calls and faking some second level dependencies
If the receiver of a static call delegates to another
collaborator, the mock rules will not be triggered
The tests can fail with no reason (false positives)
and are no longer a refactoring safety net
33
34. Static calls
Collaborators are not visible in your API
Collaborators behavior can change without
notice
Static calls can force you to register transitive
dependencies
Singletons are dependencies
34
35. No static calls, transitive dependencies
If the tests run real static calls, you might be
forced to register in the test also the static
collaborators dependencies
Example: if the collaborator uses a globally
defined WebSession object, your test should also
register it in the ThreadLocal
You might be forced to run the test in a suite
35
36. Static calls
Collaborators are not visible in your API
Collaborators behavior can change without
notice
Static calls can force you to register transitive
dependencies
Singletons are dependencies
36
37. No static calls, singletons
Singletons are provided over a static factory
method call, that can not be mocked. You need to
mock the singleton dependencies
Since the singleton behavior can change without
notice, you should treat it as a dependency
Prefer injecting beans with singleton scope. Their
method calls can also be cached
37
38. What makes your code easy to test
Loose
coupling No static
calls
38
Logic free
constructors
Compact
data
creation
Clear intent
40. Compact data creation
It is really difficult to create complex test data, if
the domain classes expose only setters and
allArgsConstructors
If you control the domain classes, make sure you
design them small and cohesive
Consider providing builders and / or with-ers as
alternative to setters
40
41. Compact data creation
Setters mean mutable objects. Use them only if
you really need data objects
The creation of Java beans in the test leads to
long set up methods, that are hard to follow and
boring to read
AllArgsConstructor calls force the tests to provide
values for all the bean fields, even if irrelevant
41
42. Compact data creation
The builders can create immutable objects
The builders let you initialize just the relevant
fields
The builders can also initialize behind the scene
fields with default values, avoiding duplicated
code in each test method
42
43. Compact data creation
The builders and with-ers calls can be chained,
increasing the code readability
Example - with-ers
Customer regular = customer()
.withName(name)
.withId(123L)
.withSubscription(456L);
43
44. Compact data creation
Example builder:
Customer company = Customer.companyBuilder()
.name(company A)
.id(1234567L)
.taxAddress(someAddress())
.mainOfficeAddress(someOtherAddress())
.build();
Customer private = Customer.privateBuilder()
.name(Joe)
.birthday(1970-01-01)
.homeAddress(some address)
.postalAddress(some other address)
.build();
44
45. What makes your code easy to test
Loose
coupling No static
calls
45
Logic free
constructors
Compact
data
creation
Clear intent
46. 46
Write clear code, not smart one
The tests and the readers of your code will be thankful
47. Clear intent, no magic
There is way more time invested in maintenance,
than in the original code development
Other developers will most likely extend your
code
The tests should document the code. The good
code is simple and its intent is clear. Avoid smart
tricks and hidden functionality
47
48. Clear intent, no magic
Smell: getter with extra functionality
class UserMonitor {
private User currentUser;
User currentUser(){
if (this.currentUser == null) {
this.currentUser = loadUser();
validationService.check(currentUser);
outboundService.scan(currentUser);
}
}
}
A whole jungle is hidden behind the getter call
48
49. Clear intent, no magic
Smell: return default values if not initialized
class User {
private final List<Subscription> subscriptions;
List<Subscription> subscriptions(){
return subscriptions == null ? trial() : subscriptions;}}
The test code would check if there is a trial
subscription for every user, that has no
subscriptions
49
50. Clear intent, no magic
Smell: add data to match client requirements
class ServiceLayerMapper {
// The user interface algorithm needs an extra empty product!
public List<Product> mapBuyedItems(){
return asList(
emptyProduct(),
mappedItems(purchasedItems())
);
}
The test code will check if there is always an item
in the basket, if the user does not buy anything
50
51. CREDITS
Growing Object-oriented software, guided by tests (Steve Freeman, Nat
Pryce)
Stephen Colebourne, Code generating beans in java:
/scolebourne/code-generating-beans-in-java
Misko Hevery, testability explorer blog:
http://misko.hevery.com/code-reviewers-guide/
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
Effective Java, Joshua Bloch
Elegant objects, Yegor Bugayenko
51
52. CREDITS
Presentation template by 際際滷sCarnival
Photographs by Unsplash
Diverse device hand photos by Facebook Design Resources
www.freepik.com photos:
Designed by mrsiraphol / Freepik
Designed by ijeab / Freepik
Designed by Onlyyouqj / Freepik
Designed by ijeab / Freepik
Designed by Dashu83 / Freepik
52