-
Notifications
You must be signed in to change notification settings - Fork 1
/
SingletonTestability.java
153 lines (131 loc) · 4.02 KB
/
SingletonTestability.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
Testability issues of using singletons in 'real' situations
Example looks at loading a single instance of a database at any one time
*/
package creational.singleton;
import com.google.common.collect.Iterables;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
interface Database
{
int getPopulation(String name);
}
class SingletonDatabase implements Database // here, we have an idea of an abstraction
{
private Dictionary<String, Integer> capitals
= new Hashtable<>();
private static int instanceCount = 0;
public static int getCount() { return instanceCount; }
private SingletonDatabase()
{
instanceCount++;
System.out.println("Initializing database");
try
{
File file = new File(
SingletonDatabase.class.getProtectionDomain()
.getCodeSource().getLocation().getPath()
);
Path fullPath = Paths.get(file.getPath(), "capitals.txt");
List<String> lines = Files.readAllLines(fullPath);
// Iterables.partition(lines, 2)
// .forEach(kv -> capitals.put(
// kv.get(0).trim(),
// Integer.parseInt(kv.get(1))
// ));
}
catch (Exception e)
{
e.printStackTrace();
}
}
private static final SingletonDatabase INSTANCE
= new SingletonDatabase();
public static SingletonDatabase getInstance()
{
return INSTANCE;
}
public int getPopulation(String name)
{
return capitals.get(name);
}
}
class SingletonRecordFinder
{
public int getTotalPopulation(List<String> names)
{
int result = 0;
for (String name : names)
{
/*
PROBLEM here is testability
The only way to test this method is with the live database.
If you remember dependency inversion principle, you should depend
on abstractions. However, this is a concrete class.
Ideally it would be with a dummy database.
*/
result += SingletonDatabase.getInstance().getPopulation(name);
}
return result;
}
}
class ConfigurableRecordFinder
{
// Introduce an abstraction, to respect dependency inversion principle
private Database database;
// Database injected into the constructor
public ConfigurableRecordFinder(Database database) {
this.database = database;
}
public int getTotalPopulation(List<String> names)
{
int result = 0;
for (String name : names)
result += database.getPopulation(name); // dependency injection; dummy database here
return result;
}
}
class DummyDatabase implements Database
{
private Dictionary<String, Integer>
data = new Hashtable<>();
public DummyDatabase() { // dummy data added here
data.put("alpha", 1);
data.put("beta", 2);
data.put("gamma", 3);
}
@Override
public int getPopulation(String name) {
return data.get(name);
}
}
class Tests
{
@Test // not a unit test, but an integration test
public void singletonTotalPopulationTest()
{
SingletonRecordFinder rf = new SingletonRecordFinder();
List<String> names = List.of("Seoul", "Mexico City");
int tp = rf.getTotalPopulation(names);
assertEquals(1750000+1740000, tp); // this test will work
}
@Test // unit test
public void dependentPopulationTest()
{
DummyDatabase db = new DummyDatabase();
final ConfigurableRecordFinder rf = new ConfigurableRecordFinder(db);
assertEquals(4, rf.getTotalPopulation(
List.of("alpha", "gamma")
));
}
}