28 August 2005

Wicket, Wicket, Wicket ! 喔!整個禮拜時間都耗在它上面,感想是... Wicket 很新,非常非常的新。我覺得與其稱他為 1.1 版,其實他大概只有 0.7, 0.8 版這個程度而已。到不是程式的品質不夠,而是一些決策性的功能還沒有完全的定案。除了核心開發人員有用在production外,其他人多半還是在研究評估的階段,也有不少是拿他當作下班後的興趣 :-) 它的文件很少,目前多半要去看他的 source 和 wiki。Javadoc 寫的很詳細,也是不錯的參考。而 wiki 上則有很多範例和經驗分享。如果真的現在要拿來開發研究,Mailing List是一定要訂閱的,而且上面幾乎是有問必答 (只是寫英文有點累啦...),整個社群算滿活躍的 (最近登上 SourceForge 一百大)

anyway, 如果要能夠取代目前我們專案中所用的 web 技術組合:SiteMesh / StrutsTestCase / Spring / ACEGI security / DisplayTag / Struts / Validation Framework... 等等。以現在 Wicket 來說還是蠻吃力的。當然啦,拿一個小 Framework 跟上面一大堆的技術來比當然是不公平的... 但還是要比較啊,誰叫 Wicket 跟上面的都不相容呢?別的 jsp-based 的 framework 只要做到一些簡單的功能就夠了,因為它們可以跟上面提到的工具整合,合起來也是嚇嚇叫。Wicket 就沒這福份了,全部得自己提供... 這也是 Wicket 主要的弱點之一。

首先,最重要的是先看看 Test 該怎麼寫。我們寫 Struts 有好用的 StrutsTestCase,Wicket 也有提供類似的工具,我們後面再提。Wicket 開發人員是比較建議用 integration test -- jWebUnit 。這種萬用型的 http 測試工具,當然 Wicket 也能搭配使用。不過我們可以搭配 Jetty Server,然後只設定 WicketServlet,這樣測試會比較快:

public abstract class JettyJWebUnitTestCase extends WebTestCase {

	private static Server server;
	private ServletContext servletContext;

	private static final String LOCALHOST = "localhost";
	private static final int CONFIDENTIALPORT = 8443;
	private static final int LOW_RESOURCE_PERSIST_TIME = 2000;
	private static final int MAX_IDLE_TIME = 30000;
	private static final int MAX_THREADS = 10;

	//啟動 jetty server 並設定 WicketServlet
	protected void startServerForMockWicketServlet() throws Exception {
		server = new Server();
		SocketListener listener = new SocketListener();
		listener.setHost(LOCALHOST);
		listener.setPort(CONFIDENTIALPORT);
		listener.setMaxThreads(MAX_THREADS);
		listener.setMaxIdleTimeMs(MAX_IDLE_TIME);
		listener.setLowResourcePersistTimeMs(LOW_RESOURCE_PERSIST_TIME);
		listener.setConfidentialPort(CONFIDENTIALPORT);

		server.addListener(listener);
		buildMockWicketServlet();
		server.start();
	}

        //關閉舊的 WicketServlet,再重新啟動一次。
	protected void restartMockWicketServlet() throws Exception {
		ServletHttpContext context = (ServletHttpContext) server
				.getContext("/wicket");
		context.stop();
		server.removeContext(context);
		buildMockWicketServlet().start();
	}

	private ServletHttpContext buildMockWicketServlet()
			throws Exception {

		//設定 webapp 為 "/wicket"
		ServletHttpContext context = (ServletHttpContext) server
				.getContext("/wicket");

		//替這個 webapp 為 加上一個Servlet: WicketServlet 
		ServletHolder holder = context.addServlet("BookApplication", 
                               "/book","wicket.protocol.http.WicketServlet");

		//下面是 WicketServlet 的啟始參數
		holder.setInitParameter("applicationClassName",
				"javaworld.wicket.BookApplication");
		servletContext = holder.getServletContext();
		return context;
	}

	protected final void stopServer() throws Exception {
		if (server != null) {
			server.stop();
			server = null;
		}
	}

}

上面的 startServerForMockWicketServlet() 建立 Jetty Server,然後 buildMockWicketServlet() 手動設定一個 WicketServlet到 Server 裡,而後再啟動 server。如此,這個 Jetty Server 就只有一個 Servlet 而已,size 小很多。每一次執行 testMethod 時,就用 restartMockWicketServlet() 重開 servlet。開關一次大概約需兩秒吧。雖然還是很慢,但是已經比啟動整個 server 好多了。而且這個 Server 也沒有 Filter/Spring config 等等的設定,所以並沒有其他的 overhead,專門用來測試 Wicket 的WebPage (如果你的 Wicket Page 裡面要用到 Spring 的 Bean,那就要花點技巧 inject 到這個沒有 Spring 的 webapp,有機會下次再說吧)

上面提到,Wicket 也有提供 without container 的測試工具。在他的 test 目錄底下,有MockWebApplication 可以使用。使用方法如下:

//正式的程式碼:
public class BookHomePage extends WebPage {
	public BookHomePage() {
		add(new Label("message", "welcome"));
	}
}

//Unit Test Method
public void testBookHome() throws Exception {

	//建立 Mock Wicket 工作環境
	WebApplication mockWebApp = new MockWebApplication(); 

	//設定欲測試的 WebPage 為首頁
	mockWebApp.getPages().setHomePage(BookHomePage.class);

        //初始化 reqeust/response
	mockWebApp.setupRequestAndResponse(); 

	//開始 request
	mockWebApp.processRequestCycle();

	//取得 page render 後的結果
	WebPage resultPage = mockWebApp.getLastRenderedPage();

	//開始 assert component 內容
	Label label = (Label) resultPage.get("message") ;
	assertEquals("welcome", label.getModelObjectAsString());
}

手續蠻複雜的,不過只要做一些功,把常用的寫成 util 就可以簡化了。這是最簡單的測試 ,如果要測試複雜的 component 例如 PagableList 會比較麻煩一點。而 Form 的測試目前還沒有什麼範例可看... orz

Test 還要花點時間研究... 暫時先這樣啦... 而 SiteMesh 的功能,Wicket 也有類似的solution,還有 Spring 的 integration.... 還有... 這些都待下次在說吧,再研究,再研究。


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>