Dummy 物件


你 的程式不會只有一個物件,某個方法在運行時通常也會與其它物件互動,你必須與其它物件互動,才能真正運行程式,方能進行測試,然而,如果你與真正的物 件互 動,那麼你就不僅是在測試目前這個物件了,而是連同互動的物件一同測試,你還得測試另一個物件的正確性,方能保證目前這個物件的正確性。

舉個例子來說,你寫了一個方法,打算傳入一個java.net.URL, 希望可以用字串傳回該網址的文件內容:
package cc.openhome;

import java.io.*;
import java.net.URL;

public class HttpHelper {
public String getContent(URL url) throws IOException {
InputStream input = url.openStream();
StringWriter writer = new StringWriter();
byte[] data = new byte[2048];
int length = -1;
while((length = input.read(data)) != -1) {
writer.write(new String(data, 0, length));
}
input.close();
writer.close();
return writer.toString();
}
}

基本上,你可以建立一個HTTP伺服器,在上面放一些文件,然後建立URL實 例時指定資源網址,然後測試傳回的字串是否真的是伺服器上放置的資源。

如果你只是想測試程式的實作內容是否正確,而不是測試真正的HTTP連結是否正確,那麼以上的作法不僅麻煩,而且你連帶測試了你的伺服器正否正常,文 件網址是否正確等。

你真正想測試的是程式實作,但又得跟URL物 件互動,事實上,你可以傳入一個假的物件,該物件的實作儘量簡單,只傳回受測程 式必要的資料即可。仔細觀察你的程式實作中,實際上真正需要的,只是從URL物 件傳回一個InputStream而 已。

這個傳入的假物件稱為Dummy物件,以上例而言,最直覺的作 法,似乎是繼承URL,並 重新定義其openStream()方法,不過URL被定義為final而無法繼承,遵照URL的規範,你應該使用URL的 setURLStreamHandlerFactory()傳入一個URLStreamHandlerFactory的實作物件。例如:
package test.cc.openhome;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;

import org.junit.BeforeClass;
import org.junit.Test;

import cc.openhome.HttpHelper;

class DummyURLStreamHandlerFactory implements URLStreamHandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u)
throws IOException {
return new DummyURLConnection(u);
}
};
}
}

class DummyURLConnection extends HttpURLConnection {
DummyURLConnection(URL u) {
super(u);
}

@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(new String("success").getBytes());
}

@Override
public void disconnect() {}

@Override
public boolean usingProxy() {
return false;
}

@Override
public void connect() throws IOException {}
}

public class HttpHelperTest {
@BeforeClass
public static void setUp() {
URL.setURLStreamHandlerFactory(new DummyURLStreamHandlerFactory());
}

@Test
public void testGetContent() throws Exception {
HttpHelper helper = new HttpHelper();
String expected = "success";
String result = helper.getContent(new URL("http://localhost"));
assertEquals(expected, result);
}

}

簡單地說,在底層,URL 會呼叫一個URLConnection的實作,並呼叫其getInputStream()取得InputStream實例,在上頭最後實作了 DummyURLConnection,但僅簡單的重新定義了getInputStream(),其它什麼作沒實作,也沒有真正開 啟HTTP,這就是Dummy物件這個名稱的由來,因為它通常很 簡單,簡單到沒什麼動作與狀態,只傳回某個結果

Dummy物件是模擬程式與現在資源互動的一種方式,由於其夠簡單,所以較容易實作,可以讓你將目標集中在真正想測試的程式上,但因為太簡單,無法反 映真正的資源或環境,如果你希望連同環境互動一同測試,也就是所謂的整合測試 (Integration test),那就不適用Dummy物件的策略。