How difficult is to automatically test the HelloWorld.
We fix it and other many difficult scenarios with techniques like:
- lower "s" singleton
- law of demeter
- dependency injection
- and more examples
3. @drpicox
@drpicox
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
HELLO WORLD
3
IMPROVE TESTING — HELLO WORLD
4. @drpicox
@drpicox
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
@Test
public void say_hello_world() {
HelloWorld.main(null);
assertThat(???)
}
HELLO WORLD
4
IMPROVE TESTING — HELLO WORLD
5. @drpicox
@drpicox
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
@Test
public void say_hello_world() {
var buffer = new ByteArrayOutputStream();
var out = new PrintStream(buffer);
System.setOut(out);
HelloWorld.main(null);
assertThat(buffer.toString()).isEqualTo("Hello Worldn");
}
HELLO WORLD
5
IMPROVE TESTING — HELLO WORLD
6. @drpicox
@drpicox
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
@Test
public void say_hello_world() {
var realOut = System.out;
var buffer = new ByteArrayOutputStream();
var testOut = new PrintStream(buffer);
System.setOut(testOut);
HelloWorld.main(null);
System.setOut(realOut);
assertThat(buffer.toString()).isEqualTo("Hello Worldn");
}
HELLO WORLD
6
IMPROVE TESTING — HELLO WORLD
7. @drpicox
@drpicox
public class HelloWorld {
private PrintStream out;
public HelloWorld(PrintStream out) {
this.out = out;
}
public void sayHello() {
this.out.println("Hello World");
}
public static void main(String[] args) {
new HelloWorld(System.out).sayHello();
}
}
HELLO WORLD
7
IMPROVE TESTING — HELLO WORLD
8. @drpicox
@drpicox
@Test
public void say_hello_world() {
var buffer = new ByteArrayOutputStream();
var testOut = new PrintStream(buffer);
new HelloWorld(testOut).sayHello();
assertThat(buffer.toString()).isEqualTo("Hello Worldn");
}
HELLO WORLD
8
IMPROVE TESTING — HELLO WORLD
9. @drpicox
@drpicox
HELLO WORLD
9
IMPROVE TESTING — HELLO WORLD
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
@Test
public void say_hello_world() {
var buffer = new ByteArrayOutputStream();
var out = new PrintStream(buffer);
System.setOut(out);
HelloWorld.main(null);
assertThat(buffer.toString()).isEqualTo("Hello Worldn");
}
10. @drpicox
@drpicox
@Test
public void say_hello_world() {
var buffer = new ByteArrayOutputStream();
var testOut = new PrintStream(buffer);
new HelloWorld(testOut).sayHello();
assertThat(buffer.toString()).isEqualTo("Hello Worldn");
}
HELLO WORLD
10
IMPROVE TESTING — HELLO WORLD
13. @drpicox
@drpicox
Create one single instance, do not use static
LOWER "S" SINGLETON
13
IMPROVE TESTING — PATTERNS
// wrong
public class House {
private static House instance;
public static House getInstance() {
if (instance == null) instance = new House();
return instance;
}
}
// correct
var house = new House();
14. @drpicox
@drpicox
Ask for what you operate; do not look for things
LAW OF DEMETER
14
IMPROVE TESTING — PATTERNS
// wrong
house.getDoor().open()
// correct
door.open();
15. @drpicox
@drpicox
Give me what I need
DEPENDENCY INJECTION
15
IMPROVE TESTING — PATTERNS
// wrong (Java)
public class Clicker {
public void click() {
House.getInstance().getDoor().open();
}
}
// wrong (Javascript)
import house from './house'
export class Clicker {
click() {
house.getDoor().open();
}
}
16. @drpicox
@drpicox
Give me what I need
DEPENDENCY INJECTION
16
IMPROVE TESTING — PATTERNS
// correct
public class Clicker {
public void click(Door door) {
door.open();
}
}
17. @drpicox
@drpicox
Give me what I need
DEPENDENCY INJECTION
17
IMPROVE TESTING — PATTERNS
// better (do not breaks method signature)
public class Clicker {
private Door door;
public void Clicker(Door door) {
this.door = door;
}
public void click() {
door.open();
}
}
19. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
19
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
var cc = new CreditCard("123...234")
cc.charge(100);
}
Throws an exception... why?
20. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
20
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
CreditCardProcessor.init(...);
var cc = new CreditCard("123...234")
cc.charge(100);
}
Throws an exception... why?
21. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
21
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
OfflineQueue.start();
CreditCardProcessor.init(...);
var cc = new CreditCard("123...234")
cc.charge(100);
}
Throws an exception... why?
22. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
22
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
Database.connect(...);
OfflineQueue.start();
CreditCardProcessor.init(...);
var cc = new CreditCard("123...234")
cc.charge(100);
}
Now works... and makes the charge...
23. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
23
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
var db = Database();
var q = new OfflineQueue(db);
var ccp = new CreditCardProcessor(q);
var cc = new CreditCard("123...234")
cc.charge(100, ccp);
}
Now works... and makes the charge...
24. @drpicox
@drpicox
Deceptive API
STATICS ARE LIARS
24
IMPROVE TESTING — STATICS ARE LIARS
testCharge() {
var db = MockDatabase();
var q = new OfflineQueue(db);
var ccp = new CreditCardProcessor(q);
var cc = new CreditCard("123...234")
cc.charge(100, ccp);
assertThat(db).hasCharge(100, cc);
}
27. @drpicox
@drpicox
public class TestHouse {
@Test
testSecureHouse() {
Door door = new Door();
Window window = new Window();
House house = new House(door, window,
new Roof(),
new Kitchen(),
new LivingRoom(),
new BedRoom());
house.secure();
assertTrue(door.isLocked());
assertTrue(window.isClosed());
}
}
27
IMPROVE TESTING — AVOID CODE ASSERTIONS
Irrelevant instances on the test
(what if they had also complex constructor?) 😱
28. @drpicox
@drpicox
public class TestHouse {
@Test
testSecureHouse() {
Door door = new Door();
Window window = new Window();
House house = new House(door, window, null, null, null, null);
house.secure();
assertTrue(door.isLocked());
assertTrue(window.isClosed());
}
}
28
IMPROVE TESTING — AVOID CODE ASSERTIONS
We do not care, faster and easier to understand
30. @drpicox
@drpicox
public class TemperatureLogger implements Logger {
public void log(Notebook notebook) {
var weatherStation = WeatherStation.getInstance();
var thermometer = weatherStation.getThermometer();
var temperature = thermometer.getTemperature();
notebook.annotate(temperature);
}
}
// It is instantiated like this:
var logger = new TemperatureLogger();
TEMPERATURE LOGGER
30
IMPROVE TESTING — FIXING EXAMPLES
31. @drpicox
@drpicox
public class TemperatureLogger implements Logger {
private WeatherStation weatherStation;
TemperatureLogger(WeatherStation weatherStation) {
this.weatherStation = weatherStation;
}
public void log(Notebook notebook) {
var thermometer = weatherStation.getThermometer();
var temperature = thermometer.getTemperature();
notebook.annotate(temperature);
}
}
// It is instantiated like this:
var logger = new TemperatureLogger(weatherStation);
TEMPERATURE LOGGER
31
IMPROVE TESTING — FIXING EXAMPLES
32. @drpicox
@drpicox
public class TemperatureLogger implements Logger {
private Thermometer thermometer;
TemperatureLogger(Thermometer thermometer) {
this.thermometer = thermometer;
}
public void log(Notebook notebook) {
var temperature = thermometer.getTemperature();
notebook.annotate(temperature);
}
}
// It is instantiated like this:
var logger = new TemperatureLogger(
weatherStation.getThermometer()
);
TEMPERATURE LOGGER
32
IMPROVE TESTING — FIXING EXAMPLES
33. @drpicox
@drpicox
public class WheelTester {
private LegalRules legalRules;
public WheelTester(LegalRules legalRules) {
this.legalRules = legalRules;
}
public boolean checkWheels(Car car) {
Thikness thikness = legalRules.getThikness();
List<Wheel> wheels = car.getWheels();
for (Wheel wheel: wheels) {
boolean isOk = wheel.hasThikness(thikness);
if (!isOk) return false;
}
return true;
}
}
WHEEL TESTER
33
IMPROVE TESTING — FIXING EXAMPLES
34. @drpicox
@drpicox
public class WheelTester {
private Thikness thikness;
public WheelTester(Thikness thikness) {
this.thikness = thikness;
}
public boolean checkWheels(Car car) {
List<Wheel> wheels = car.getWheels();
for (Wheel wheel: wheels) {
boolean isOk = wheel.hasThikness(thikness);
if (!isOk) return false;
}
return true;
}
}
WHEEL TESTER
34
IMPROVE TESTING — FIXING EXAMPLES
35. @drpicox
@drpicox
public class WheelTester {
private Thikness thikness;
public WheelTester(Thikness thikness) {
this.thikness = thikness;
}
public boolean checkWheels(List<Wheel> wheels) {
for (Wheel wheel: wheels) {
boolean isOk = wheel.hasThikness(thikness);
if (!isOk) return false;
}
return true;
}
}
WHEEL TESTER
35
IMPROVE TESTING — FIXING EXAMPLES
36. @drpicox
@drpicox
public class HDFormatterA {
private HD hd;
public HDFormatterA() {
hd = new HardDisk();
}
public void doWork() {
hd.open();
hd.format("FAT");
hd.close();
}
}
HD FORMATTER
36
IMPROVE TESTING — FIXING EXAMPLES
37. @drpicox
@drpicox
public class HDFormatterB {
private HD hd;
public HDFormatterB() {
hd = HardDisk.getInstance();
}
public void doWork() {
hd.open();
hd.format("FAT");
hd.close();
}
}
HD FORMATTER
37
IMPROVE TESTING — FIXING EXAMPLES
38. @drpicox
@drpicox
public class HDFormatterC {
private HD hd;
public HDFormatterC() {
hd = ServiceLocator.get(HD.class);
}
public void doWork() {
hd.open();
hd.format("FAT");
hd.close();
}
}
HD FORMATTER
38
IMPROVE TESTING — FIXING EXAMPLES
public void test_hdformatterc() {
var hd = new MockHD();
ServiceLocator.set(HD.class, hd);
new HDFormatterC().doWork();
assertThat(hd.verify()).isTrue();
}
39. @drpicox
@drpicox
public class HDFormatterD {
private HD hd;
public HDFormatterD(HD hd) {
this.hd = hd;
}
public void doWork() {
hd.open();
hd.format("FAT");
hd.close();
}
}
HD FORMATTER
39
IMPROVE TESTING — FIXING EXAMPLES
public void test_hdformatterd() {
var hd = new MockHD();
new HDFormatterD().doWork(hd);
assertThat(hd.verify()).isTrue();
}