在Java 中,容器(Container)的表面意涵,代表著一個Java寫的程式,實質上,容器抽象了環境的概念。JVM是Java 程式唯一認得的虛擬作業系統,容器則是運行於這個作業系統上的Java程式,代表著某個環境資源。例如,Web容器,代表 著運行於JVM虛擬作業系統上的虛擬HTTP伺服器,是Servlet/JSP唯一認識的HTTP伺服器。
那麼你要怎麼測試與容器互動的服務?例如,你寫了個Servlet:
package cc.openhome;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String user = req.getParameter("user").trim();
String passwd = req.getParameter("passwd").trim();
String page = "login.html";
if("justin".equals(user) && "1234".equals(passwd)) {
page = "success.html";
}
req.getRequestDispatcher(page).forward(req, resp);
}
}
那麼你要怎麼測試這個Servlet的運作?實際作好相關設定、部署(Deploy)至容器,然後開啟瀏覽器執行?這已經步入功能測試(Functional test)的範圍,而非單元測試,你不僅測試 了Servlet,連同部署設定是否正確等,都一併測試了。
可以嘗試以 Dummy 物件 的概念來進行測試。例如:
package
test.cc.openhome;
import cc.openhome.LoginServlet;
import java.security.Principal;
import static org.junit.Assert.*;
import org.junit.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
class TestForLoginServlet extends LoginServlet {
public void doTest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
class DummyHttpServletRequest extends HttpServletRequestWrapper {
private Map<String, String> parameters;
private String forwardToPage;
private boolean isForwarded;
public DummyHttpServletRequest(Map<String, String> parameters) {
super(new HttpServletRequest() {
// 一些方法本體為空的實作
// 純綷滿足HttpServletRequestWrapper建構的要求
});
this.parameters = parameters;
}
@Override
public String getParameter(String name) {
return parameters.get(name);
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
forwardToPage = path;
return new RequestDispatcher() {
public void forward(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
isForwarded = true;
}
public void include(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
}
};
}
public String getForwardToPage() {
return forwardToPage;
}
public boolean isForwarded() {
return isForwarded;
}
}
public class LoginServletTest {
private TestForLoginServlet loginServlet;
@Before
public void setUp() {
loginServlet = new TestForLoginServlet();
}
@Test
public void testLoginSuccess() throws Throwable {
Map<String, String> param = new HashMap<String, String>();
param.put("user", "justin");
param.put("passwd", "1234");
DummyHttpServletRequest dummyRequest =
new DummyHttpServletRequest(param);
loginServlet.doTest(dummyRequest, null);
assertTrue(dummyRequest.isForwarded());
assertEquals("success.html", dummyRequest.getForwardToPage());
}
@Test
public void testLoginFail() throws Throwable {
Map<String, String> param = new HashMap<String, String>();
param.put("user", "someone");
param.put("passwd", "1234");
DummyHttpServletRequest dummyRequest =
new DummyHttpServletRequest(param);
loginServlet.doTest(dummyRequest, null);
assertTrue(dummyRequest.isForwarded());
assertEquals("login.html", dummyRequest.getForwardToPage());
}
}
import cc.openhome.LoginServlet;
import java.security.Principal;
import static org.junit.Assert.*;
import org.junit.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
class TestForLoginServlet extends LoginServlet {
public void doTest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
class DummyHttpServletRequest extends HttpServletRequestWrapper {
private Map<String, String> parameters;
private String forwardToPage;
private boolean isForwarded;
public DummyHttpServletRequest(Map<String, String> parameters) {
super(new HttpServletRequest() {
// 一些方法本體為空的實作
// 純綷滿足HttpServletRequestWrapper建構的要求
});
this.parameters = parameters;
}
@Override
public String getParameter(String name) {
return parameters.get(name);
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
forwardToPage = path;
return new RequestDispatcher() {
public void forward(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
isForwarded = true;
}
public void include(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
}
};
}
public String getForwardToPage() {
return forwardToPage;
}
public boolean isForwarded() {
return isForwarded;
}
}
public class LoginServletTest {
private TestForLoginServlet loginServlet;
@Before
public void setUp() {
loginServlet = new TestForLoginServlet();
}
@Test
public void testLoginSuccess() throws Throwable {
Map<String, String> param = new HashMap<String, String>();
param.put("user", "justin");
param.put("passwd", "1234");
DummyHttpServletRequest dummyRequest =
new DummyHttpServletRequest(param);
loginServlet.doTest(dummyRequest, null);
assertTrue(dummyRequest.isForwarded());
assertEquals("success.html", dummyRequest.getForwardToPage());
}
@Test
public void testLoginFail() throws Throwable {
Map<String, String> param = new HashMap<String, String>();
param.put("user", "someone");
param.put("passwd", "1234");
DummyHttpServletRequest dummyRequest =
new DummyHttpServletRequest(param);
loginServlet.doTest(dummyRequest, null);
assertTrue(dummyRequest.isForwarded());
assertEquals("login.html", dummyRequest.getForwardToPage());
}
}
最主要的是Servlet中,實際上需要的是從HttpServletRequest取 得請求參數,因此設計DummyHttpServletRequest讓Servlet的測試得以運行,就這個例子而言,自行設計 Dummy物件是有些麻煩,另一方式,就是使用Mock框架所提供的Mock物件,這之後會再談到。
然而事實上,HttpServletRequest的 實作是由容器提供,容器的行為實際上更為複雜,容器所管理的物件亦有其生命週期等議題,如果你要測試的,並 不是上面那個簡單的Servlet,那麼用Dummy物件或Mock物件,皆不足以代表實際容器所提供的物件,你所需要的,是從容器中獲取更貼近部署環境 的物件。
可以使用 Embedded 資源 的方式,實際運行一個嵌入式容器,想辦法從中獲取相關資源進行測試。例如:
package test.cc.openhome;
import static org.junit.Assert.*;
import java.io.IOException;
import java.net.URL;
import javax.servlet.*;
import javax.servlet.http.*;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.*;
import cc.openhome.LoginServlet;
class TestForLoginServlet extends LoginServlet {
public void doTest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
class DummyHttpServletRequest extends HttpServletRequestWrapper {
private String forwardToPage;
private boolean isForwarded;
public DummyHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
forwardToPage = path;
return new RequestDispatcher() {
public void forward(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
isForwarded = true;
}
public void include(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
}
};
}
public String getForwardToPage() {
return forwardToPage;
}
public boolean isForwarded() {
return isForwarded;
}
}
public class LoginServletTest {
private Server server;
private DummyHttpServletRequest dummyRequest;
@Before
public 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 {
dummyRequest = new DummyHttpServletRequest(request);
new TestForLoginServlet().doTest(dummyRequest, response);
baseRequest.setHandled(true);
}
});
server.start();
}
@After
public void tearDown() throws Exception {
server.stop();
}
@Test
public void testLoginSuccess() throws Throwable {
URL url = new URL("http://localhost:8080/?user=justin&passwd=1234");
url.openStream().read();
assertTrue(dummyRequest.isForwarded());
assertEquals("success.html", dummyRequest.getForwardToPage());
}
@Test
public void testLoginFail() throws Throwable {
URL url = new URL("http://localhost:8080/?user=someone&passwd=1234");
url.openStream().read();
assertTrue(dummyRequest.isForwarded());
assertEquals("login.html", dummyRequest.getForwardToPage());
}
}
在上面,請求會發送給內嵌的Jetty容器,Jetty容器產生HttpServletRequest、 HttpServletResponse,你將所需的HttpServletRequest包 裹為DummyHttpServletRequest物 件,再產生Servlet進行測試,並驗證測試結果。
這是容器內(In-container)測試的基本概念,你互動的資源或物件,是從實際的容器取得,這樣的測試,更貼近於整合測試(Integration test),因為你 所獲取的物件或資源更貼近於實際環境,可以得到更可靠的測試結果。
在容器內測試框架的部份,Servlet/JSP可以使用Cactus,JSF 可以使用JSFUnit(擴充了Cactus),OSGi則有JUnit4OSGi,在EJB測試的部份,若需要以嵌入式的方式來執行測試, 可以瞭解一下OpenEJB。