際際滷

際際滷Share a Scribd company logo
Write testable code
best practices and common smells
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
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!
What makes your code easy to test
Loose
coupling No static
calls
4
Logic free
constructors
Compact
data
creation
Clear intent
What makes your code easy to test
Loose
coupling No static
calls
5
Logic free
constructors
Compact
data
creation
Clear intent
Ensure components loose coupling
Hard coupling severely damages testability
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
What makes your code easy to test
Loose
coupling No static
calls
24
Logic free
constructors
Compact
data
creation
Clear intent
Logic free constructors are easy to test
Avoid smart, or hard working constructors
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
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
What makes your code easy to test
Loose
coupling No static
calls
28
Logic free
constructors
Compact
data
creation
Clear intent
29
The instance method calls are easy to fake
Use them as long as possible, avoid static calls
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
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
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
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
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
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
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
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
What makes your code easy to test
Loose
coupling No static
calls
38
Logic free
constructors
Compact
data
creation
Clear intent
39
The tests need to create data
Builders are better than setters
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
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
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
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
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
What makes your code easy to test
Loose
coupling No static
calls
45
Logic free
constructors
Compact
data
creation
Clear intent
46
Write clear code, not smart one
The tests and the readers of your code will be thankful
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
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
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
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
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
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
Any questions?
You can find me at:
marian.wamsiedel@gmail.com
53THANKS!

More Related Content

Write testable code in java, best practices

  • 1. Write testable code best practices and common smells
  • 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
  • 6. Ensure components loose coupling Hard coupling severely damages testability
  • 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
  • 25. Logic free constructors are easy to test Avoid smart, or hard working constructors
  • 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
  • 39. 39 The tests need to create data Builders are better than setters
  • 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
  • 53. Any questions? You can find me at: marian.wamsiedel@gmail.com 53THANKS!