6. F.I.R.S.T.
Fast Run hundreds or thousands per second
Isolated Failure reasons become obvious
Reliable Run repeatedly in any order, any time
Self-validating No manual evaluation required
Timely Written before the code
9. Smell: Constructor does real work
class Showtimes {
MovieRepository repository;
Showtimes() {
repository = MovieRepositoryFactory.getInstance();
}
Set<Movie> findMovies(City city) {
return repository.findByCity(city);
}
}
10. Smell: Constructor does real work
class Showtimes {
MovieRepository repository;
Showtimes() {
repository = MovieRepositoryFactory.getInstance();
}
Set<Movie> findMovies(City city) {
return repository.findByCity(city);
}
}
11. Smell: Constructor does real work
class Showtimes {
MovieRepository repository;
Showtimes() {
repository = MovieRepositoryFactory.getInstance();
}
Set<Movie> findMovies(City city) {
return repository.findByCity(city);
}
}
12. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
13. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
14. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
15. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
16. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
17. Factory Pattern, boilerplate
public class MovieRepositoryFactory {
static class MovieRepositoryHolder {
static MovieRepository instance = new MovieRepositoryImpl();
}
public static MovieRepository getInstance() {
return MovieRepositoryHolder.instance;
}
static void setInstance(MovieRepository mock) {
MovieRepositoryHolder.instance = mock;
}
}
18. Hard to test
@Test
void findMovies_cantTestYouDude() {
Showtimes showtimes = new Showtimes();
// FRAK!
// No database setup... I'm stuck with my collaborators
// Can't really change it... Will it run in my
// partners computer?
}
26. Fix: MovieRepository creation
class App {
public static void main(String[] args) {
Injector injector = Guice.createInjector(
new AbstractModule() {
@Override void configure() {
bind(MovieRepository.class)
.to(JpaMovieRepository.class);
}
});
// Injection is VIRAL! Avoid at all cost using the
// Injector in more than one place (aka Service Locator)
injector.getInstance(Showtimes.class).findMovies(bcn);
}
}
27. Fix: Testable and flexible design
Showtimes showtimes;
@Mock MovieRepository repository;
@Test void findMovies_easyNow() {
showtimes = new Showtimes(repository);
given(repository.findByCity(bcn))
.willReturn(ImmutableSet.of(looper));
Set<Movie> movies = showtimes.findMovies(bcn);
assertThat(movies, hasItem(looper));
}
28. Fix: Testable and flexible design
Showtimes showtimes; //SUT
@Mock MovieRepository repository;
@Test void findMovies_easyNow() {
showtimes = new Showtimes(repository);
given(repository.findByCity(bcn))
.willReturn(ImmutableSet.of(looper));
Set<Movie> movies = showtimes.findMovies(bcn);
assertThat(movies, hasItem(looper));
}
33. Flavour: Partial objects, Law of Demeter
class Ratings {
User user;
Ratings(AccountManager manager) {
user = manager.getLoggedUser();
user.setQueue(new ItTakesForeverToBuildOSSQueue());
}
}
34. Flavour: Partial objects, Law of Demeter
class Ratings {
User user;
Ratings(AccountManager manager) {
user = manager.getLoggedUser();
user.setQueue(new ItTakesForeverToBuildOSSQueue());
}
// Test? Slow, really slow
// We don't use AccountManager anywhere else
}
35. Fix: Partial objects, Law of Demeter
class Ratings {
User user;
@Inject Ratings(User user) {
this.user = user;
}
}
36. Fix: Partial objects, Law of Demeter
class Ratings {
User user;
// How do we inject this User like we need it?
// Where's the Queue?
@Inject Ratings(User user) {
this.user = user;
}
}
37. Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
User user = manager.getLoggedUser();
user.setQueue(queue);
return user;
}
@Provides @Singleton
Queue providesLongRunningQueue() {
return new ItTakesForeverToBuildOSSQueue();
}
38. Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
User user = manager.getLoggedUser();
user.setQueue(queue);
return user;
}
@Provides @Singleton
Queue providesLongRunningQueue() {
return new ItTakesForeverToBuildOSSQueue();
}
39. Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
User user = manager.getLoggedUser();
user.setQueue(queue);
return user;
}
@Provides @Singleton // no JVM Singleton, yay!
Queue providesLongRunningQueue() {
return new ItTakesForeverToBuildOSSQueue();
}
40. Fix: Partial objects, Law of Demeter
@Provides
User providesUser(AccountManager manager, Queue queue) {
User user = manager.getLoggedUser();
user.setQueue(queue);
return user;
}
@Provides @Singleton // no JVM Singleton, yay!
Queue providesLongRunningQueue() {
return new ItTakesForeverToBuildOSSQueue();
}
// Provider methods should be inside a Module
41. Test: Partial objects, Law of Demeter
Ratings ratings; //SUT
@Test void ratings_findAverage() {
ratings = new Ratings(new DummyUser());
// Easy to test now, we can have a mock or test-double
// and check everything about Ratings. We don't care
// about collaborators.
}
42. Warning signs: Constructor does real work
New keyword in constructor or fields
Static method calls
Anything more than field assignments
Object not fully initialized after constructor
Control flow
49. Flavour: Digging around
class TicketPriceCalculator {
MovieTheaterService service;
TicketPriceCalculator(MovieTheaterService service) {
this.service = service;
}
// Problem? Imagine this in a big application, your code
// will be much more complicated than you (and your team) need
BigDecimal calculatePrice(User user, Invoice invoice) {
UserBalance balance = user.getBalance();
BigDecimal amount = invoice.getSubtotal();
return service.calculateTotal(balance, amount);
}
}
50. Fix: Digging around
class TicketPriceCalculator {
MovieTheaterService service;
TicketPriceCalculator(MovieTheaterService service) {
this.service = service;
}
// Cleaner API, forget about "Middle men" class
BigDecimal calculatePrice(UserBalance balance, BigDecimal am) {
return service.calculateTotal(balance, am);
}
}
51. Fix: Digging around
TicketPriceCalculator calculator; //SUT
@Mock MovieTheaterService service;
@Test void calculatePrice_tooMuchWork() {
calculator = new TicketPriceCalculator(service);
UserBalance balance = new DummyBalance("1.0");
BigDecimal amount = new BigDecimal("9.3"));
BigDecimal result = calculator.calculatePrice(balance, amount);
assertThat(result, is(new BigDecimal("8.3"));
}
53. Flavour: "Context", "Container"...
class MovieTheaterService {
void processOrder(UserContext context) {
User user = context.getUser();
Order order = context.getOrders().byUser(user).now();
order.process();
}
}
54. Flavour: "Context", "Container"...
class MovieTheaterService {
void processOrder(UserContext context) {
User user = context.getUser();
Order order = context.getOrders().byUser(user).now();
order.process();
}
}
55. Flavour: "Context", "Container"...
class MovieTheaterService {
void processOrder(UserContext context) {
User user = context.getUser();
Order order = context.getOrders().byUser(user).now();
order.process();
}
// Test? Create a UserContext with ton of mocks and stuff
}
56. Flavour: "Context", "Container"...
class MovieTheaterService {
void processOrder(UserContext context) {
User user = context.getUser();
Order order = context.getOrders().byUser(user).now();
order.process();
}
// Test? Create a UserContext with ton of mocks and stuff
}
57. Fix: "Context", "Container"...
class MovieTheaterService {
void processOrder(User user, Order order) {
order.process();
}
// Test? Sure! You got everything you need in place.
}
58. Warning signs: Digging into Collaborators
Objects to use other objects, not themself.
Having to create mocks that return other mocks
Law of Demeter everywhere
Seeing more than one period (.) in a method
chaining. Going deep in the object graph:
mgs.getSolid().findOcelot().now()
Exception: DSLs
Suspicious names
Context, Environment, Container...
60. Flavour: They are liars!
@Test void chargeCreditCard() { // Example: @mhevery
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
}
61. Flavour: They are liars!
@Test void chargeCreditCard() {
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
// Fails at runtime:
// java.lang.NullPointerExpection
}
62. Flavour: They are liars!
@Test void chargeCreditCard() {
CreditCardProcessor.init();
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
}
63. Flavour: They are liars!
@Test void chargeCreditCard() {
CreditCardProcessor.init();
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
// Argh! This is getting awkward
// java.lang.NullPointerExpection
}
64. Flavour: They are liars!
@Test void chargeCreditCard() {
CreditCardProcessor.init();
OfflineQueue.start();
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
}
65. Flavour: They are liars!
@Test void chargeCreditCard() {
CreditCardProcessor.init();
OfflineQueue.start();
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
// Are you kidding?
// java.lang.NullPointerExpection
}
66. Flavour: They are liars!
@Test void chargeCreditCard() {
Database.connect("hsqldb");
CreditCardProcessor.init();
OfflineQueue.start();
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
}
67. Flavour: They are liars!
@Test void chargeCreditCard() {
Database.connect("hsqldb"); // Global State
CreditCardProcessor.init(); // Global State
OfflineQueue.start(); // Global State
CreditCard cc = new CreditCard("9999 0000 7777", 5, 2009);
assertThat(cc.charge(30.0), is(true));
// How do we know this? Sure it's documented somewhere... right?
}
68. How do you provide global variables in languages
without global variables?
"Don't. Your programs will
thank you for taking the
time to think about design
instead."
-- Kent Beck,
JUnit and TDD creator
71. Fix: They are liars!
Dependency Injection
Dependency Injection
72. Fix: They are liars!
Dependency Injection
Dependency Injection
Dependency Injection
73. Fix: They are liars!
Dependency Injection
Dependency Injection
Dependency Injection
Dependency Injection
74. Fix: They are liars!
@Test void chargeCreditCard() {
Database db = mock(Database.class);
Queue queue = new OfflineQueue(db);
CreditCardProcessor ccProc = new CreditCardProcessor(queue);
CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
cc.charge(30.0);
}
75. Fix: They are liars!
@Test void chargeCreditCard() {
Database db = mock(Database.class);
Queue queue = new OfflineQueue(db);
CreditCardProcessor ccProc = new CreditCardProcessor(queue);
CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
cc.charge(30.0);
}
76. Fix: They are liars!
@Test void chargeCreditCard() {
CreditCardProcessor ccProc = mock(CreditCardProcessor.class);
CreditCard cc = new CreditCard(ccProc, "9999 0000 7777");
cc.charge(30.0);
}
77. About Singletons
Singletons and JVM Singletons
Gang of four, static instance field.
Best known as an Anti-pattern.
But there's nothing wrong about having one
instance of an Object
Application Singleton
Managed by someone, eg: Guice
@Singleton scope
78. About Global State
Global state is Evil
Dirty design, enables Action at a distance (anti-pattern),
extremely-hard-to-test, etc.
Caveat: When it's ok?
Primitive or immutable constant reference
static final String DEVFEST = "Devfest rocks"
When information only travels one way
org.slf4j.Logger // but hard to test
79. Warning signs: Global State and Singletons
Using Singletons
Static fields or static methods
Don't confuse with helper or factory methods
Static initialization block
class App {
static {
MutableObject object = Registry.find();
}
}
Service Locators
83. Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
84. Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
85. Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
Quijote
86. Smell: Class does too much, aka
Kitchen Sink
Dumping Ground
This Class does this AND that AND ...
God Class
Quijote
"You can look at everything except this
Class"
87. Smell: Class does too much
interface MovieTheater {
void open();
void addShowtime(Showtime showtime);
Set<Showtime> getShowtimes();
void purchaseTicket(Buyer buyer, Movie movie);
UserBalance findUserBalance(User balance);
void processOrder(Order order);
// ...
}
88. Smell: Class does too much
interface MovieTheater {
void open();
void addShowtime(Showtime showtime);
Set<Showtime> getShowtimes();
void purchaseTicket(Buyer buyer, Movie movie);
UserBalance findUserBalance(User balance);
void processOrder(Order order);
// ...
}
89. Fix: Class does too much
interface MovieTheater {
void open();
void addShowtime(Showtime showtime);
Set<Showtime> getShowtimes();
}
interface BillingService {
void purchaseTicket(Buyer buyer, Movie movie);
UserBalance findUserBalance(User balance);
void processOrder(Order order);
}
90. Warning signs: Class does too much
Descriptive names for this smell
Tiny scroll bar
Many fields, many methods
Use Sonar and LCOM4 (Lack of Cohesion) to check this
Many collaborators
Just don't getting the Class
94. Much more Guice!
JSR330
javax.inject -- Google + Spring effort
Roboguice
Android, @InjectView, @InjectResource
Dagger
From Square, original Guice committers,
built on JSR330, pretty exciting!
95. References
Writing testable code Miško Hevery
Working effectively with legacy code Michael Feathers
The testability Explorer blog Blog, Miško Hevery
Dependency Injection Book, Dhanji R. Prasanna
Test Driven - Practical TDD for Java devs Book, Lasse Koskela (2007)
Effective Unit Testing - For Java devs Book, Lasse Koskela (2012)
xUnit Test Patterns Book, Gerard Meszaros
Painless Testing Slides, in Java or Python. Johannes Seitz