
其實(shí)我不是很理解為什么用了Spring還需要使用靜態(tài)方法來提供單例之類的服務(wù)。也許是傳承自較老的代碼,也許對(duì)Spring對(duì)象的生命周期不太肯定,也許要與不屬于Spring上下文的代碼共享邏輯??傊F(xiàn)實(shí)常常還是能看到這種代碼的。
/**
*提供靜態(tài)方法的類
*/
public class PersonPool {
private static PersonPool instance;
static {
instance = new PersonPool();
}
private PersonPool() {
// ...
}
public static PersonPool getInstance() {
return instance;
}
public String next() {
throw new RuntimeException("resource not available in test");
}
}
/**
*使用靜態(tài)方法的服務(wù)
*/
public class SampleService {
public String greeting() {
String name = PersonPool.getInstance().next();
return "Hello " + name;
}
}
一旦這些靜態(tài)方法中用到了環(huán)境相關(guān),需要在測(cè)試中隔離的資源,寫單元測(cè)試就時(shí)就會(huì)遇到困難。
這類代碼是無法用mockito來mock的。PowerMock可以mock靜態(tài)方法,不過使用中會(huì)發(fā)現(xiàn)會(huì)給測(cè)試引入更多的細(xì)節(jié),這些細(xì)節(jié)都是妨礙表達(dá)測(cè)試意圖的噪音。
而且往往mock掉方法本身,又發(fā)現(xiàn)類初始化有個(gè)坑……,一點(diǎn)點(diǎn)解決下去,最后測(cè)試湊合能跑起來,但是充滿了各種和實(shí)現(xiàn)緊密耦合的細(xì)節(jié)。實(shí)現(xiàn)代碼里兩行換個(gè)順序,也許測(cè)試就不工作了。
其實(shí)這種處境,往往是因?yàn)楸е?strong>“不能為了測(cè)試修改實(shí)現(xiàn)代碼”的信念造成的。
如果拋開這個(gè)假設(shè),對(duì)代碼稍作修改,測(cè)試就不再是個(gè)問題了。
/**
*提供靜態(tài)方法的類保持不變
*/
public class PersonPool {...}
/**
*增加適配類
*/
@Component
public class PersonPoolProvider {
public PersonPool getInstance() {
return PersonPool.getInstance();
}
}
/**
*改用自動(dòng)注入的適配對(duì)象
*/
public class SampleService {
@Autowired
PersonPoolProvider personPoolProvider;
public String greeting() {
String name = personPoolProvider.getInstance().next();
return "Hello " + name;
}
}
可以看到只需要對(duì)實(shí)現(xiàn)代碼進(jìn)行影響很小的修改:
- 未改變?cè)徐o態(tài)方法的執(zhí)行邏輯和生命周期。
- 不影響其它使用靜態(tài)方法的代碼。
- 適配類足夠簡(jiǎn)單,無需測(cè)試。
希望這不是個(gè)為遺留代碼補(bǔ)測(cè)試的權(quán)宜之計(jì),而是進(jìn)一步改進(jìn)已有代碼的開端。
有以下問題可以探討:
- util類、方法是否應(yīng)該有某種資源的所有權(quán)?
- 現(xiàn)實(shí)中單個(gè)存在的事物是否就一定用單例實(shí)現(xiàn)?
- 使用單例時(shí),是在用它“單個(gè)實(shí)例”的保證,還是在利用可以從類名獲得實(shí)例的“可訪問性”?