Dummy 物件是極為簡單的物件,目的在隔離互動物件或資源對目前測試的影響,以 Dummy 物件 中的範例來說,你在測試的 過程中,並沒有真正開啟HTTP連結。
如果你想更進一步測試URL協定為HTTP時,是否能真正開啟HTTP連結來下載資料,那麼就不適用Dummy物件,但因為某些原因,你並不想安裝一個真 正的HTTP伺服器。
在測試過程中,可以改採另一種嵌入式(Embedded)資源。舉例來說,你可以在運行測試的過程中,運行一個簡單的嵌入式HTTP伺服器,這個伺服器簡 單到只符合你的HTTP協定交換需求。
Tomcat或Jetty都可以用嵌入式的方式,在測試過程中運行起來,以 Jetty 為例,你可以如下建立單元測試:
package test.cc.openhome;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import cc.openhome.HttpHelper;
public class HttpHelperTest {
private static Server server;
@BeforeClass
public static void setUp() throws Exception {
server = new Server(8080);
server.setHandler(new AbstractHandler() {
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().print("success");
}
});
server.start();
}
@AfterClass
public static void tearDown() throws Exception {
server.stop();
}
@Test
public void testGetContent() throws Exception {
HttpHelper helper = new HttpHelper();
String expected = "success";
String result = helper.getContent(new URL("http://localhost:8080"));
assertEquals(expected, result);
}
}
@BeforeClass的 方法中,會啟動嵌入式的Jetty伺服器,預設傾聽在8080埠(Port),@AfterClass則關閉伺服器。在這 個測試類別中,使用預設的URL行為,底層會開啟HTTP協定與Jetty伺服器溝通。
如果想使用Maven來管理Jetty的相依程式庫,可以在POM中加入:
<dependencies>
...
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>7.2.0.v20101020</version>
<scope>test</scope>
</dependency>
</dependencies>
Jetty本身亦是Web容器(Container),所以也可用在 Servlet/JSP測試,這之後會在談到。
附帶一提的是,為了方便,你可以在測試時隱藏這類嵌入式資源建構的細節。例如參考 擴充 BlockJUnit4ClassRunner 的內容,若可以這樣撰寫測試類別的話更好:
package test.cc.openhome;
import static org.junit.Assert.assertEquals;
import java.net.URL;
import org.junit.Test;
import org.junit.runner.RunWith;
import cc.openhome.HttpHelper;
@RunWith(value = JettyRunner.class)
public class HttpHelperTest {
@JettyHandler(value = SuccessHandler.class)
@Test
public void testGetContent() throws Exception {
HttpHelper helper = new HttpHelper();
String expected = "success";
String result = helper.getContent(new URL("http://localhost:8080"));
assertEquals(expected, result);
}
}
在使用 @JettyHandler指定Jetty的Handler類別,這是自行擴充的標 註:
package test.cc.openhome;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JettyHandler {
Class<? extends AbstractJettyHandler> value();
}
例如指定的是SuccessHandler:
package test.cc.openhome;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
public class SuccessHandler extends AbstractJettyHandler {
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().print("success");
}
}
AbstractJettyHandler 是自定義的類別,擴充自AbstractHandler類別,目的在提供預設的連接埠:
package test.cc.openhome;
import org.eclipse.jetty.server.handler.AbstractHandler;
public abstract class AbstractJettyHandler extends AbstractHandler {
private int port = 8080; // default port 8080
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
為了讓標註可以運 作,你必須自訂Runner:
package test.cc.openhome;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
public class JettyRunner extends BlockJUnit4ClassRunner {
public JettyRunner(Class<?> clz) throws InitializationError {
super(clz);
}
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
JettyHandler jettyHandler = method.getAnnotation(JettyHandler.class);
JettyStatement statement = null;
if(jettyHandler != null) {
try {
AbstractJettyHandler handler =
(AbstractJettyHandler) jettyHandler.value().newInstance();
statement = new JettyStatement(
super.methodInvoker(method, test), handler);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
return statement;
}
}
其中 JettyStatement的定義為:
package test.cc.openhome;
import org.eclipse.jetty.server.Server;
import org.junit.runners.model.Statement;
public class JettyStatement extends Statement {
private Statement invoker;
private AbstractJettyHandler handler;
public JettyStatement(Statement invoker, AbstractJettyHandler handler) {
this.invoker = invoker;
this.handler = handler;
}
@Override
public void evaluate() throws Throwable {
Server server = new Server(handler.getPort());
server.setHandler(handler);
try {
server.start();
invoker.evaluate();
}
finally {
if(server != null) {
server.stop();
}
}
}
}