2008年12月27日 星期六

[Interview] Infopower 英丰寶 面試經驗分享2

經過上次面試後,大約過了4天人資小姐又打電話來。帶著不安的心情我接起了電話,(內心OS:該不會要被發卡了吧!),哈!運氣很好的,是第二次面試!很興奮的約好了時間再去英丰寶大戰一場!

不過這次的面試不是很順遂,因為第一次約的時間,眼看自己準備要出發去,結果人資小姐又打電話來說:主管臨時有會議,所以要改時間!哇勒XD,主管是有沒有這麼忙阿!好吧,那就在換時間囉~

第二次面試前的五個小時,人資又打電話來,不過我沒有接到!心理一陣涼意,該不會是有要改時間吧!這該不會是要考驗一個人耐心的兵法吧~再度帶著不安的心情回撥電話,結果人資卻跟我說:沒有啦!只是要確認你下午會來喔!哇靠~差點沒嚇死我 囧

到了英丰寶,還是被請到上次面試的那間會議室,人資就說:總經理在開會,請你在多等十分鐘喔!等等~我有沒有聽錯!這次是總經理喔~天阿!大咖的出現了,等等還有更猛的~

這一等就是30分鐘,原來人資說的話要乘上3倍(開玩笑的!)

總經理一進來後,先確認完名字無誤後就先說了一句話:以往能夠到我這一關的人不超過 10 個!這句話有兩個含意:第一,這是誇獎(科);第二,我是最後決定你去留的人!我慌了~果然是來頭不小的魔王~

一開始就先問,為何要選擇研發替代役啦!然後你跟別人有怎樣不一樣的地方,也就是要你 promote 一下自己等等的

之後就談談論文的東西,還問我前瞻性~偏偏我們實驗室做的東西還很新,未來的發展要看市場,最後我就把老闆曾說過的 semantic web 願景大略的說給了經理聽!

接下來就是問問家庭背景啦!父母親的工作、從父母身上學到的東西等等~然後就是一連串的說英丰寶跟外面的軟體公司有些啥不一樣的~話中還偷偷嗆新店的 ERP 公司(我想大家應該都很清楚)~

然後就是換我打擊啦~不過我只有問一個問題(原本準備了兩個,因為經理的氣勢實在是太強了!第二問題我就忘了問):公司員工的年資!經理說:核心的研發人員最久的有 9 年,最年輕當然有不到一年,公司人員的流動不太大,不知道是真是假,知情的人分享一下吧~

這場面試就這樣結束了!好快,才 30 分鐘左右,感覺很不錯,只是經理會從頭盯著你的眼睛看,似乎是想要從說話中找出你有沒有在說謊~問的問題都會層層的過濾你是否是公司要找的人!總之,我覺得壓力很大~

後記~回家後上網查了一下,這位所謂的總經理就是公司了創辦人阿~天阿~遇到最終大魔王了!

以上,分享給想要去英丰寶面的人~

2008年12月24日 星期三

[Agile Method] 少量而高品質的Document

今天 meeting 時,老闆剛好有提到 agile method 的 document 觀念,我就順便問了一下上次去英丰寶面試時被問到的其中一個問題:當我們在開發專案時,我們要怎樣知道哪些文件是該寫?當時我回答的內容是:在專案開發時,如果認為某些文件是需要撰寫的,那就去寫,如果有哪些文件是過時或是無法在繼續維護與同步的話,就要大膽的刪除!不過,在 agile method 中強調,最重要的文件其實就是 pseudocode,也就是程式中的註解。因為 pseudocode 才是 developer 當初寫程式時的思維!這點很重要,雖然原先的 agile method 中並沒有硬性規定,但是我在實驗室中開發 ITRI 計畫的實際經驗來看,pseudocode 的確很重要,因為使用者常常會更改系統的需求,如果當初有寫好 pseudocode,修改程式所花費的時間就會大幅的降低!這點我很肯定!

講了這麼多,老闆今天給我的回應是:當我們在開發專案時,無論開發系統是否有特定的使用者,都需要先從 scenario 下手,也就是先將系統的使用情境撰寫出來,這是所有文件的起頭!因為有了 scenario 之後,我們就可以根據 scenario 撰寫出 use cases(跟 use case diagram 一點關係都沒有!),這些 use case 是將 scenario 中所描述的功能以程式的角度去描述!也就是定好 input, output 與呼叫的關係。接下來就是根據 use case 寫出 class interface ,這裡的 class interface 是指撰寫出空的 class 架構與 method signature(含 parameters),再來就是根據 use case 中的輸入輸出撰寫 test code,完成了 test code 之後就是寫 class 中每個 method 的 pseudocode,最後才是補上 source code。

上面所說得是基本的文件,如果開發團隊之間的溝通能力較弱的話,可以就得視團隊情況去撰寫需要的文件,如:class diagram 等。但是如果團隊溝通的能力較強,也就是團隊成員之間都清楚這個系統的詳細資訊的話,文件能不多寫就不要浪費力氣,因為修改使用者要變更的需求是需要花更多的力氣!

但是如果使用者要求一些超越基本文件的範圍,那當然還是要撰寫,畢竟使用者是最大的!給錢的永遠是老大~

今天還有問到 UML 圖到底要不要去寫的問題。老闆的想法是:完全不用!因為系統開發完之後,我們可以透過逆向工程工具幫助我們由完成的 code 去產生 class 與 sequence diagram,當然,系統完成後我們也不必多花時間來作逆向工程來產生 UML 圖,而是有需要再去作~畢竟,少量的文件是 agile method 的想法,但是要以高品質為前提!

對於文件的觀念,agile method 與 CMMI 完全是站在不同立場上去思考的!我個人是比較贊成 agile method 的看法,這並不是因為我們實驗室是採用 agile method 的關係,我本身也有上過 CMMI 的課程,雖然不是很專精,不過看到 CMMI 需要撰寫很多的文件就很令人頭疼!在台灣,很多公司為了獲得 CMMI 的資格,文件都是有目的在寫的!老實說,身為工程師我想沒有人是喜歡寫文件的,畢竟寫文件是一種吃力不討好的工作。但是如果文件與系統沒有同步,一百頁的文件也形同浪費硬碟空間的垃圾~文件太多,同步所花費的時間與力氣就越多,所以要精簡文件的數量。

以上是小弟個人淺見~希望有興趣的人可以多多討論!

2008年12月20日 星期六

[Struts2] 在Eclipse中開發Struts2 web project

首先,以下我所使用的eclipse版本為3.3。

1. 在eclipse的command line中我們按下[File]->[New]->[Other…],如圖一

clip_image002

圖一 [Other…]

2. 按下other後會出現如圖二的畫面後,在web之下選擇Dynamic Web Project。

clip_image004

圖二 Dynamic Web Project

3. 按下 Next 之後,出現圖三的畫面,我們就輸入我們所期望的project name。剩下的設定我們就採用預設值。

clip_image006

圖三 New Dynamic Web Project

4. 接著就按下Finish。我們在Package Explore中的看到Eclipse幫我們建立的樹狀結構如圖四。

clip_image008

圖四 Struts2 web project樹狀結構

5. 接著,我們就到 Struts2 官方網站下載最新的Struts2程式。將下載的檔案作解壓縮後,我們在資料夾中會有apps的資料夾,如圖五。

clip_image010

圖五 Struts2官方檔案解壓縮後

6. 在apps資料夾中我們將struts2-blank.war中的/WEB-INF/lib底下的所有檔案複製到我們剛剛所建立的web project中相對應的位置,也就是/WEB-INF/lib之下。

7. 接著我們就對我們的web project進行refresh,對project按下滑鼠右鍵,在選單中選擇Refresh,如圖六。

clip_image012

圖六 更新project

8. 再來就是要讓我們的web server可以在啟動我們的web app時啟動Struts2的filter。所以我們要在/WEB-INF/web.xml中加入:

<!-- Struts 2 default filter and filter mapping -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


9. 所以完成後的web.xml檔案內容會如圖七。關於web.xml的相關設定請參考tomcat的說明。



clip_image014



圖七 web.xml



10. 再來我們還需要一個struts.xml檔案,這個檔案是Struts2的主要設定檔,在我們的web project中的src資料夾按下右鍵選擇[New]->[Other…],出現的視窗如同圖二,選擇XML中的XML。然後將新增的檔名設定為struts.xml,這個檔名是不能隨意更改的,不然Struts2會讀取不到。struts.xml檔案內容我們就放入:



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
</struts>


11. 這樣就完成初步的佈署了!為了之後要在開發上的方便,我們先將此web project為出成一個war檔案。對我們的project按下右鍵並選擇Export,如圖八。



clip_image016



圖八 匯出專案



12. 接著我們就將project匯出成WAR file,所以選擇Web之下的WAR file,如圖九。



clip_image018



圖九 WAR file



13. 按下Next後,我們要將檔案匯出到我們的目的地。所以設定Destination。指定好儲存位置後就按下Finish。這樣我們之後就可以利用匯入的方式使用了!

2008年12月13日 星期六

[Interview] Infopower 英丰寶 面試經驗分享

星期一收到人資寄信邀請我去面試,因為我有投這家公司的研發替代役!收到來信的我當然是超級興奮的!因為我還沒有正式的面試過~雖然接 case 前會有面試,不過那樣的面試對我來說並不算是真正的面試!

Infopower 英丰寶,公司負責開發一些 enterprise solution。自己點連結進去看一下吧~如果你有興趣。公司不大,資本額也算小的,有興趣的人就自己去查一下 104 的黃頁吧!

那天人資寄信給我時,有特別交代要準備投影片,內容還有規定~大致上就是如何將自己所學的東西貢獻給公司以及專業技術方面的經驗等!當然,我也是依照這樣的方式去準備~面試前自己可是超級緊張的~

一進到公司裡,就看到有些人站著在聊天喝咖啡,感覺氣氛非常融洽!進到了會議室後,總機小姐就拿了一份考卷給我,「時間三十分鐘,答案紙下方可以做計算」,這跟我在網路上查到的不一樣耶~大家都好像只有面試而已,沒有考試耶XD 該不會是我程度太差~不過我覺得考卷很簡單,都是考一些 JAVA 的程式題,細心度與觀念一定要夠,這樣應該就 OK 了!

交卷後,另一位感覺年輕的工程師進來跟我面談,結果,他帶的筆電沒有裝 office,囧。所以他就去載了個 viewer 來使用。

在下載檔案之餘,他就先希望我廣泛的描述一下我曾經修過哪些課是印象深刻的?說說自己的感想。我們就以很輕鬆的對話開始了這場 30 分鐘的面試。

開啟簡報後,我原本想要依照我的方式去報告,不料!對於我所學的東西,有一樣是他沒有興趣的,所以他就說:抱歉喔!今天不是要來上XX技術的課程!你這部份就精簡的描述一下吧!哎呀~完全打亂了我的節奏!還好我還可以接受,接下來的一張投影片,他就非常有興趣。他說:好!這張你不用講,我直接問你問題好了!雖然接下來的問題我回答的不盡理想,不過他也沒有顯露出"你真的在亂蓋"的表情,所以還是OK。

最後我就分享一下我接 case 的經驗,他會提出一些經驗上得問題來考考我!

進入最後一張投影片後,他就開始一連串的提出技術性的問題!有一題我其實只有回答一半:以你撰寫 JAVA 五年的經驗來說,你覺得 JAVA 的優缺點在哪裡?哇!這題好難,我只有回答他關於 JAVA 的好處,壞處我就說其實我沒有仔細想過~感覺這裡就扣好多分喔!><

另外就是寫程式遇到最難解的 bug 是哪個?Struts and Struts2 的差別?你以後想要做什麼?大概就是這些問題~能回答的我就盡量回答囉!

最後他就問問我對於公司有沒有問題阿?我就問了他們幾點上下班。他回答完後就反問我:你為什麼會 care 公司的上下班時間?我就跟他說,因為我也希望我有自己的時間!(心想:爆了) 結果他說:這跟公司當初成立的理念相符合,操爆員工是不對的!

總結,今天的面試其實很愉快~面試的工程師人很好,很仔細的聽我的回答~不會硬要追著我回答錯的部份窮追猛打!也很廣泛的說明了公司的特色與研發替代役在公司的角色等等。這家公司雖然小,不過工作氣氛感覺很不錯!重點是他們家所有產品的核心技術都是自己研發的!也就是自己有在研發產品的關鍵零組件,公司的前景是否光明?我也不知道~

2008年12月6日 星期六

[Discussion] Why has the Web become the Default Development Platform?

不知在哪一天,我得知了 InfoQ 這網站,我覺得這個網站的內容很棒,也很先進,因為每天會有許許多多的人在上面分享自己的看法,有點類似討論區,不過又有點不一樣,我也說不上來!在這網站中我知道了很多最新資訊!

Why has the Web become the Default Development Platform?是我最近看到的一篇文章,我就有感而發一下吧~

文章中提到 DWR 的創始者 Joe Walker,曾經提出一項看法:Web 已經成為系統開法者首選的開發平台。這裡所謂的開發平台不是指 JAVA 或 C 的開發環境,而是系統的佈署與介面的顯示環境。

Joe 提出這樣的看法有他的理由,Web 平台與生俱有的特性:1) 系統容易佈署、2) 簡單的 UI 介面開發、3) 單純的 HTML 語法與 4) 開放性的平台架構,這些都讓 Web 一躍而晉身成為系統開發者的首選平台。

Google 工程部門的副總,Jeff Huber 也提出同樣的說法:(內容我無法完全聽懂XD)

Jeff Huber 提到:如果你還是在思考採用哪種平台會比較好,你就還活在遠古時代的思維。因為 Web 已經勝出了!Web 就是未來的開發平台。

讓我們延續上面 Joe 所提到的四點特行來探討。

1. 系統容易佈署。系統的佈署曾經是開發者頭疼的問題之一,因為機器上 OS 的異質性,讓 programmer 經常要為了 portable 問題而費盡心思,不過,Web 平台就是可以讓 programmer 不必在費心於這樣的問題。因為 Web 的平台可以達到 zero installation。就像我們透過 J2EE 開發 Web 技術一樣,所有的 WAR 檔都交給 web server(如:Jakarta Tomcat) 來幫我們佈署,我們只要將檔案丟給 web server,他就會幫我們處理一切的佈署事宜。甚至,我們無須要求 client 端安裝任何的軟體,因為 OS 都具備有 browser 軟體,client 端可以不用有壓力的使用 web 平台上的東西。

2. 簡單的 UI 介面開發。HTML 語法已經行之有年,並且經過了許許多多的研究幫忙提供與開發更好用更方便的標準與技術,例如:DHTML、CSS、Javascript或是近年來走紅的 AJAX 等。這都讓開發者可以簡單的基於這些標準或技術之上來達到系統的目的。甚至,在 J2EE 應用裡,有許許多多的 OpenSource 也提供了更快速更方便與更直覺的開發框架,如:Velocity 或 FreeMarker 等介面的框架。

3. 單純的 HTML 語法。如同上述,HTML 原本的用途在於簡單的將資料能夠在網路上顯示,所以提供的語法採用 Markup Language 來表示,這不僅僅讓使用者可以快速上手,也是 machine readable。

4. 開放性的平台架構。Web 最基本的就是 HTTP Protocol,這也是 Internet 中使用最頻繁的 protocol 之一。所以只要系統佈署後,就可以讓系統在網路上被搜尋到與使用到。

除了這四點之外,我個人認為 Web 平台還有一項優勢:Web 平台尚以經有許多的 framework 讓系統的架構可以更容易開發與維護。在 J2EE 應用中有許多的 framework,如:Struts, WebWork, Spring MVC 等,都致力於讓 Web 系統符合 MVC design pattern(也就是 separate of concern 的概念),如果開發者採用 Web 作為系統的開發平台,那就可以更進一步的選用 OpenSource 的 MVC framework,這樣讓系統更具有 robust 與 reliability。而 MVC 架構在傳統的 Application 中並沒有現成的 framework 可以使用,這導致系統開發者要自己處理 MVC 中很 detail 的事項,或者就是要自己開發 MVC framework。

以後小弟在各網站之間有看到哪些好玩的訊息,在分享給大家吧~

如果您有任何想法或指教,您可以留言或寫 mail 來一起討論與切磋喔~

2008年12月4日 星期四

[DWR] Direct Web Remoting - Using Object

上次有提到簡單的使用 DWR 了! 不過上次的範例是採用 String 作為 server 端服務的回傳值,那如果我們要回傳的結果不只是 String 而是物件的話,DWR 能幫我們處理嗎?

這問題,DWR 當然都幫我們解決了! 而且我們要撰寫這樣的 code 並不難,跟我們撰寫 String 回傳值有 99% 相似! 這就是 DWR 的功力啦~

首先,我們改寫上次的 DWRServer class,增加新的 method:

public class DWRServer {
    public String sayHello(String name) {
        return "Hello! "+name;
    }
    public HelloObject sayHelloObject(String name) {
        HelloObject hello = new HelloObject();
        hello.setYourName(name);
        hello.setMyName("Silver8250");
        return hello;
    }
}
class HelloObject {
    private String yourName;
    private String myName;
    //setter and getter methods
}

這是我們增加一個 sayHelloObject() method 這個 method 會回傳一個 HelloObject (我們定義在下方) 的物件。HelloObject 中儲存前端使用者傳入的 name 與作者我的 name,裡面的 methods 都是針對 yourName 與 myName 變數的 getter 與 setter methods,在此就不列出(不懂? 趕快去了解一下 JavaBean 吧!)。

後端程式寫完後,依照慣例就是設定我們的 dwr.xml 檔案:

<dwr>
    <allow>
        <create javascript="myHelloServer" creator="new">
            <param name="class" value="DWRServer"></param>
        </create>
        <convert converter="bean" match="HelloObject"></convert>
    </allow>
</dwr>

在這裡我們並不增加新的 create tag,因為我們並沒有增加新的 server 端 class,我們只是在既有的 class 上新增加 method,不過要注意的是因為我們的 method 回傳的是一個 JavaBean(也就是 HelloObject),所以我們要在 dwr.xml 告知 DWR 這件事。所以我們使用 <convert> tag,並且設定 converter attribute 的值為 bean,這樣 DWR 就知道我們是用 JavaBean,而且 match 的值要放 HelloObject class 的所在位置(當然要包含 package)。這完成後,前端就可以使用這樣的 JavaBean 來取值囉!

前端的程式我們依舊要加入:

<script src='/MyProject/dwr/interface/myHelloServer.js'></script>

<script src='/MyProject/dwr/engine.js'></script>

然後我們在前端的 javascript 更改為:

function callSayHello(myName) {
    myHelloServer.sayHelloObject(myName, showResult);
}
function showResult(result) {
    document.getElementById("resultTextMyName").value = result.myName;
    document.getElementById("resultTextYourName").value = result.yourName;
}

原先的呼叫就更改為 sayHelloObject,因為 parameter 沒有差異,所以不變。但是神奇的是在 showResult function,我們一樣放 result 作為 argument,不過裡面的呼叫就改了,因為現在的 result 不是 String 而是我們的 HelloObject 物件,我們要取值,就直接呼叫 result.myName,就可以。DWR 會自動幫我們去到 server 呼叫 result.getMyName() 然後回傳! 神奇吧~

其實,用物件作為回傳值其實跟用 primary type 作回傳值是類似的,差別在於回傳的結果在前端的頁面可以有像物件般的曲裡面的 fields 的功能!

我的 DWR 目前也只有 survey 到此,往後有機會再深入的 survey,日後在分享給大家~

[DWR] Direct Web Remoting

最近在中大電算中心接的 CASE 中,因為是一個 web platform 的校務系統,又剛好加上 AJAX 的技術,後來 PM 希望可以將原先的 AJAX 改為使用 DWR framework,所以就 survey 了一下這個 framework。

大學時期,上過 Web 2.0 的課程,當初有聽過 DWR 這樣的 framework,只知道可以讓 Java programmer 很快速的開發 AJAX 上的應用,不過當初授課的教授建議我們不要去使用這樣的東西,原因我就忘記了!不過當初 DWR 的版本還是第一版的,可能有一些安全性的問題吧!不過現在已經演進到 DWR 2.0 版本了!

講了一堆無關緊要的東西~讓我們就趕快切入主題吧!

DWR 全名為 Direct Web Remoting,官方網站:http://directwebremoting.org/,由官網的標題就可以知道 DWR 目標在於讓 Java programmer 可以很容易很輕鬆的開發 AJAX 的應用。

DWR 所能夠做到 AJAX 範圍實在廣大,不過因為我只有簡單的使用,所以就將我 survey 到最基礎的技術分享一下吧!

首先,不用我說應該也知道,就是要先下載 DWR 的 JAR 檔案!從網站上的連結就可以取得最新的 dwr.jar 檔案。由於我所使用的 web framework 是 Struts 1,所以我們就將 dwr.jar 放置於 {web project} \ WEB-INF \ lib 之下。

接下來就是要設定一下專按下的 web.xml 檔案,讓 dwr framework 可以在我們開發的 web project 啟動時被抓到。所以我們在 web.xml 中加入:

<servlet>

  <servlet-name>dwr-invoker</servlet-name>

  <servlet-class>

    org.directwebremoting.servlet.DwrServlet

  </servlet-class>

</servlet>

<servlet-mapping>

  <servlet-name>dwr-invoker</servlet-name>

  <url-pattern>/dwr/*</url-pattern>

</servlet-mapping>

更多的 web.xml 設定檔案請按我。設定完成後,我們還要建立一個 dwr.xml 檔案,並放置於 WEB-INF\ 之下,dwr.xml 是設定我們在 server 端的服務與物件,讓前端可以呼叫後端服務的設定檔。在 DWR 2.0 中引進了 annotation的機制,讓我們可以不用撰寫 dwr.xml,有興趣的人可以自行去 survey 這部份!(跟 Struts2 好像~)

我們在 dwr.xml 中的第一行要先加入:

<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://directwebremoting.org/schema/dwr20.dtd">

這樣就可以讓 eclipse 幫我們檢查與產生輔助工具。

接著我們就可以先來撰寫一個 server 端的程式,我們可以很輕巧的建立一個 server 端,因為在 DWR 之下我們並不需要 extends 或 implemented 任何的 class 或 interfaces。這樣我們在開發程式上並不會受到限制,也就是說,我們可以 reuse 過去的 code 等。

public class DWRServer{

    public String sayHello(String name) {

        return "Hello! "+name;

    }

}

這樣的 server 程式夠簡單吧!我還是遵照市面上書籍的慣用 startup example 來示範!

有了後端的程式後,我們當然就要先設定一下我們的 dwr.xml 檔案,這樣在前端的 javascript 才可以呼叫與使用。所以我們就在 dwr.xml 中寫入:

<dwr>

    <allow>

        <create javascript="myHelloServer" creator="new">

            <param name="class" value="DWRServer"></param>

        </create>

    </allow>

</dwr>

dwr.xml 中的 root node 就是 <dwr>,這沒有為什麼,因為就是 DTD 描述的規定。接下來我們就放置 <allow>,告知 DWR 有哪些 server 服務是可以讓前端存取的,與 <allow> 平行的 tags 還有 <init> 與 <signature>,我在這裡就不多加介紹,因為目前在我們的範例中並不會使用到,不過我想聰明的你應該可以大致上猜到這兩個 tags 的用途,因為這蠻直觀的!

再來,我們就要開始描述我們的後端 server 了,所以我們使用 <create> tag,從名稱上我們可以解釋為建立一個新的 server 端服務給前端使用,然後我們可以要指定一個名稱讓前端存取,這個名稱我們要撰寫在 allow tag 中的 javascript attribute 的值,就如同我所寫的 myHelloServer,這個名稱可以隨機取,不過每個 allow tag 彼此的 javascript attribute 的值不可以重複!然後我們要在 allow tag 中寫入 creator="new" 的 attribute 與 value,這應該就是告知 DWR 這是要建立服務的!

有了服務的名稱後,我們在要 allow tag 中 nested 一個 param tag,用來傳入一些參數給 DWR,必要的就是指定 name="class" 與 value 為我們撰寫 server 程式的名稱(要包含 package,我們在這裡使用 default package,所以沒有寫入),這樣 DWR 才可以知道這服務實際上是要呼叫哪個 class。當然,我們可以傳入的 param tag 還可以有其他的設定值。

後端完成後,那我們就要走到前端囉!

前端的撰寫其實也是很簡單,因為我們可以不用自己處理 AJAX 在不同瀏覽器之間的差異性,DWR 都會幫我們去完成這樣複雜且無趣的工作!

回到正題,撰寫前端的 JSP 頁面時,我們必須先在前面加入兩個 javascript 的 references:

<script src='/MyProject/dwr/interface/myHelloServer.js'></script>

<script src='/MyProject/dwr/engine.js'></script>

首先是第一個,這是要告之前端的頁面說我們剛剛設定的服務要匯入,你一定會感到很神奇,我們根本就沒有撰寫 myHelloServer.js 這檔案阿!不過這就是 DWR 會自動去幫你虛擬的產生~至於第二個就比較簡單,因為這是 DWR 必要的一些資源,所以一定要加入的。

接下來就是前端的重頭戲囉~我們要如何在前端呼叫後端的服務呢?相信我,看完接下來的範例後,你一定會驚嘆不已!這真是太神奇了~

在前端我們撰寫以下的 javascript:

function callSayHello(myName) {

    myHelloServer.sayHello(myName, showResult);

}

function showResult(result) {

    document.getElementById("resultText").value = result;

}

第一個 function 是讓我們呼叫 server 端的,我們在裡面可以直接使用 myHelloServer 來當作一個物件,然後呼叫 sayHello,並將參數傳入!有沒有很像在寫 server 端的 JAVA 程式呢?超直觀的~那你一定會問,我們的 sayHello() method 只需要一個 parameter 阿?第二個參數目的?第二個參數是要讓 DWR 在執行完結果後可以 call back 的 function,這樣當我們的呼叫完成後,前端頁面就會呼叫 showResult function。

第二個 function 就是負責將 server 回傳的結果顯示,此 function 的 argument result 是必要的!這是為了接收 server 端的結果之用。我們將 server 回傳的結果簡單的顯示某個 HTML element 上!

簡單的範例就此結束了!神奇吧~我第一次寫完後也覺得超神奇的!

Server 端的回傳不僅僅可以是一個 primary type 或 String,我們也可以放置物件作為回傳參數!這部份,就待續吧~

2008年11月25日 星期二

[Struts2] Struts 2 in Action

最近開始在研讀這本 manning 出的 Struts 2 in Action image

由於先前已經有 Struts 1 的實務經驗,所以在念這本書的期間確實觀念上要上手快很多。

一開始我以為 Struts 2 是相容於 Struts 1 的,結果完全不是這麼回事,Struts 由第一代進步到第二代,老實說,只有名字跟 design pattern 與名字有傳承而已。看到這裡,很多人心都涼了~我也是,我以為有相容,並且有提供更好的 components support。不過沒關係,基本上想法還是差不了太遠(就我目前 survey 三分之一的感觸),基本上還是以 MCV 架構為主。

所謂 MVC 就是 Model-View-Controller 的縮寫,也就是 separate of concern的概念,MCV 架構的宗旨就是希望將程式開發團隊區分成兩部分,一部分是專門負責 view 端,也就是使用者介面端的設計;另一部分就是專門撰寫程式中核心的 business logic 元件,也就是 model 端。那你一定會問,controller 誰來寫勒? 這就是牽涉到上面所說的兩隊人馬必須要協調的事項,model 端的人要與 view 端的人協調關於哪些 business logic 的結果要放到哪個 UI 上作顯示的動作。所以 controller 算是整個系統中的中樞,所以在 MVC 架構中,由 controller 為 entry point,並且決定哪個動作需要執行哪些 models 與呼叫哪些 views 來顯示。

回到主題,Struts 2 這次的開發團隊,拋棄了原先 Struts 1 的設計想法,採用 OpenSymphony 的 WebWork framework 為基礎架構。引進一些新的想法來降低 coupling 與提高 cohesion (註),像是 interceptor 就是一個很好的例子。往後我會慢慢的將這本書中我所讀到的、有感觸的部分依照各章節來說明。

下圖一是 Struts 1 的架構圖。

image

圖一 Struts 1 架構圖

圖一的流程:

1. 首先使用者會透過 browser 向 web server 的 controller 發出 request。

2. controller 就是一個 programmer 撰寫一個 class extends Action 的程式,該程式會根據使用者的請求到 Model 端的 DataObject 要求資料,這裡的 DataObject 我是意旨負責與資料庫取資料或是負責 business logic 的 class。

3. DataObject 會到 Database 中取得相關的資料。

4. DataObject 將結果回傳給 Controller。

5. Controller 根據資料選擇某個 View 來顯示給使用者。

6. 使用者透過 browser 看到一個經過處理後的頁面,並且將頁面要求的資料填入,可能是一個 form 的表單。

7. Struts framework 會將頁面上的資料帶到 Model 端的 Form,這裡的 Form 是一個 class extends ActionForm 的程式。

8. Struts framework 會將此 Model 的 Form 交給 Controller,接下來就是重複 2~4 的動作來完成另一次的 request。

下圖二則是 Struts 2 的架構圖,這張圖示依照這本書上的圖來重新繪製的。

image

圖二 Struts 2 架構

圖二的流程:

1. 同圖一,使用者下 request 給 Controller,此時的 Controller 不同於 Struts 1 的自己撰寫,而是 Struts 2 framework 來 support。

2. Controller 根據 configuration 檔案的描述來呼叫對應的 Model 的 Action 物件。

3. Model 端的 Action 物件會包含 data 與 business logic,這點與 Struts 1 就有很大的差別。簡而言之,Struts 2 的 Action 就是結合了 Struts 1 的 Controller 與 Form 物件。

4. 接著 Action 會決定要顯示哪個頁面。

5. 頁面將 Action 中的 data 排列在頁面上後顯示給使用者。

在此,就可以看出來 Struts 1,2 之間的最明顯差異性,Struts 2 將資料的存放與 business logic 合併在一起。不過這只是其中一點的差異。

註:Coupling and Cohesion

Coupling 就是指 classes 之間的相依性,若相依性越高,代表要使用此 class 需要其他 class support 就越多,這樣此 class 的 reuse 就越不易,所以我們希望程式中的 coupling 要越低越好,也就是此 class 知道別的 classes 的資訊越少越好。

Cohesion 是指單一 class 中的功能單純度,也就是說此 class 功能越單純,相對的 class 中的 fields 之間的凝聚力越高,因為每一個 fields 的設立都是為了完成某一項功能而產生的,所以凝聚力會越高。若是一個 class 擁有的能力越不單純,他的凝聚力就會越低。例如:class Person 中提供了計算某人的薪資與計算某人的年齡,所以 Person 中會有兩個 fields:salary 與 birthday,但是因為計算薪資與計算年齡之間並沒有任何的關聯,所以 salary 與 birthday 就沒有任何的凝聚力,所以這樣的設計是不好的。

2008年10月12日 星期日

[Displaytag] Decorator,裝飾器

如果你用了 displaytag 一段時間後,就會發現,他真的很好用!不過,有時候看起來彈性好像不夠。例如我希望某一個 column 的資料能夠有個 hyper link 到某一頁面,不過我需要傳送的 URL 中需要包含超過一個以上的 parameters,那我該怎麼辦?因為 displaytag column tag 只有提供一組 parameter 可以讓我們指定傳送的資料。

這問題的解法有很多種,目前我是使用過兩種。

第一種方法就是指定 displaytag table tag 中的 uid attribute,該 attribute 是用來讓我們 assign a variable 對於每一行的 row data,那這樣又怎樣勒?這樣就可以讓我們在外部使用 EL 的方式來取得該 row data 中的所有 column data 囉!到這裡應該可以瞭解吧!總而言之,如果我們指定 uid = "obj",那我們就可以在我們的程式中這樣用:

<display:table uid="obj" ...>

    <display:column ...>

        <a href="/Test.do?a=${obj.a}&b=${obj.b}" >link</a>

    </display:column>

</display:table>

第二種方法就是使用 displaytag 所提供的 decorator,我們可以建立一個 class extends TableDecorator,這樣我們就可以在 displaytag 的 table tag 中指定 decorator attribute 到我們剛剛建立的 class。

並且我們在此 class 中撰寫一個 method,此 method 需要遵守 JavaBean 的命名規則。

public String getLink() {

    MyObj obj = (MyObj) super.getCurrentRowObject();

    return "<a href=\"/Test.do?a="+obj.getA()+"&b="+obj.getB()+"\">";

}

上面程式中的第一行中,假設我們傳送給 displaytag 的物件是一個由 MyObj 物件所組成的 List 物件,所以每次 displaytag 在執行到 column tag 時,會迭代此 List 物件,並取出 MyObj 物件,所以我們的 getLink() method 可以藉由呼叫 super.getCurrentRowObject() method 來取得目前正迭代到的 MyObj 物件。因為 super.getCurrentRowObject() method 是回傳 Object 物件,所以我們需要作一次的 Casting。接著有了目前正迭代的物件,我們就可以在程式中拼湊出我們所需要的連接網址,也就是範例中的 anchor tag,當然,如果您想得更快,您也可以在這裡拼湊出一段 javascript 傳送到前端。

後端的程式完成後,我們要如何讓此 method 與前端接軌呢?除了指定 displaytag 的 table tag 中的 decorator attribute 外,我們還需要對相對應的 column 指定他的 property 為 link,也就是如下:

<display:column property="link" ... />

超簡單的!這樣的方式可以很輕易的 reuse,也就是在其他 displaytag 的頁面中我們可以指定某個 column 的 property 為 link,就可以為該頁面提供一個同樣的連結。

不過,如果你只是想要純粹將該 column 的 value 作一個顯示上不一樣的樣式話,例如數字要顯示出千分位符號。那你就不需要使用 table decorator,也就是你不需要使用方才我們所建立的 class,你可以改用 DisplayColumnDecorator,這不是一個 class 而是一個 interface,所以我們要改建立一個 class implements DisplayColumnDecorator。要產生這樣的 class,我們當然需要 implement 一個

decorate(java.lang.Object columnValue, javax.servlet.jsp.PageContext pageContext, MediaTypeEnum media)

method,因為這是官方的 API 中的規定。由此 method 的 parameters 中我們可以發現,我們可以直接取得此 column 在前端中指定的 value,而且我們可以取得 JSP 中 page scope 的物件,也就是我們可以在前端將資料儲存在 page scope,到了此 column decorator 我們就可以再次取回某些儲存在 page scope 中的資料。另外的 media 是可以得知 export 的型態。

所以由 parameter 我們可以著手進行我們的包裝作業了。我們在 decorate method 中將 columnValue 先轉換成 int 在透過 DecimalFormat class 進行數字的千分位符號加入:

DecimalFormat df = new DecimalFormat("###,###");
return df.format((int) columnValue);

同樣的,後端的程式完成後,前端我們就在我們需要進行包裝的 column 中指定 decorator attribute 到剛剛的 class 就可以了!

同樣的,使用 decorator 可以達到 reuse 的效果,讓系統在維護上更加的方便。好方法與大家分享之~

2008年10月9日 星期四

[Eclipse Plug-in] Jadclipse

會不會經常在用 Eclipse 時,發現錯誤訊息出現在 console 時,找半天就是沒有看到自己寫的 class 出現在錯誤中勒?反倒是看到一堆 third-party library 或是 jdk 的 classes。我想你如果經常使用 Eclipse 在開發系統,一定會常常碰到這樣的情形。很巧,今天推薦一個 Eclipse 的 Plug-in 叫做 Jadclipse,他原本就是要作 Jad (Java Decompiler),顧名思義,就是幫助我們將程式反組譯。

Jadclipse 的官方網站

如果你是用 Eclipse Europa(3.3 版),你就要下載 Jadclipse 3.3 的 plug-in,並且將下載的 .jar 檔案放到 Eclipse 資料夾底下的 plugin 資料夾中。然後使用 console 執行 eclipse.exe -clean 指令,這道指令會告知 Eclipse 去偵測是否有新的 plug-in 被加入 plugin 資料夾中。接著就要在下載 Jad 主程式 下載好的程式直接將其解壓縮,並且放到一個未來不會被誤刪的資料夾中(如:Program Files 資料夾底下),接著我們就要在 Eclipse 的 Preference 中設定。

如下圖一:

image

圖一 開啟 Preference 的方式

接著就會開啟 Preference 的設定視窗,在視窗中我們要找到 Java 項目底下的 JadClipse,並且我們設定其中的 Path to Decompile 項目的值為 剛剛安裝的 Jad.exe 的檔案位置。如下圖二:

image

圖二 JadClipse 設定 Path to Decompile 視窗

最後我們要設定檔案的關聯性,因為我們要將所有的 .class 檔案都交給 Jadclipse 去幫我們反組譯,所以我們在一樣的 Preference 視窗中的 General 項目下的 Editors 的 File Associations 中找到 *.class 預設給 JadClipse 開啟。如下圖三:

image

圖三 設定檔案關聯性視窗

設定完成後,以後就可以開啟 .class 檔案而不會是一堆不能開啟的檔案了!

好工具與大家分享之~

2008年10月8日 星期三

[SQL] Table Rotation(資料表旋轉)

看到主題你多少會感到很酷!因為 table 也可以旋轉喔?那麼神奇~

沒錯,如果你經常對於 table 要下 query,偏偏 table 又不是你設計(如果是你設計的那就活該啦XD),你可能會看到項這種的 table:

id type value
王大名 A 100
王大名 B 50
李大砲 A 40
李大砲 C 60
陳一二 D 20

 

這時候你想要整理出一個 table 如下:

id A B C D
王大名 100 50 NULL NULL
李大砲 40 NULL 60 NULL
陳一二 NULL NULL NULL 20

 

這時候你就會需要 table rotation 囉!有沒有一點感覺啦~由上面的 table 要改成下面的 table,就好像資料表做了旋轉的感覺。

回歸到 SQL 語法層面,我們該怎樣去作勒?若你是用 MS SQL 2005 那你可以鬆一口氣了!因為他有新的語法可以支援這樣的 table rotation - PIVOT and UNPIVOT。不過這裡不討論這兩種語法,我們要用最純粹的、最傳統、最正港的 SQL 語法來兜。

先假設第一個 table 就做 table Test,所以我們要取出成第二個 table 的樣式,所以我們在 SELECT 中一定會出現 id,A,B,C,D 的東西:

SELECT id,A,B,C,D

FROM test

因為 id 本來舊屬於 test Table 的資料,所以我們的目標就會在 A,B,C,D 這四個 column。如果我們要取出 column A 那我們就是要去取 test Table 中 type=A 的資料,所以目標很清楚,我們要在上面的 SQL 中在加入 sub-select:

SELECT id,

  A=(SELECT value FROM test WHERE type='A'),...

FROM test

這樣看似完成了,不過還是差一點,因為我們並沒有指定說是要誰的 A 值!所以要將其改成

SELECT T.id,

  A = (SELECT value FROM test WHERE type='A' AND id=T.id),

  B = (SELECT value FROM test WHERE type='B' AND id=T.id),..

FROM test AS T

GROUP BY T.id

哇!這樣就完成了~其實思考的過程並不難,重點是要知道 sub-select 的運用。

[Displaytag] 數字排序,外部排序

Displaytag 的確可以讓我們省下分頁功能的撰寫,並且透過輕鬆簡單的設定就可以完成。

不過,當你面對到數字的排序時,你就會發現頭大了!為甚麼 850 會比 1001 大?這下問題嚴重了,有經驗的人一看就知道這是因為 displaytag 排序的方式是由左至右比較編碼(我也不知道是用 ASCII 還是哪種方式),總之,排序出來的結果就不是我們要的!

首先我們需要先建立一個新的 class 他主要是用來比較出真正的數值,而不是用編碼的方式來比較,那為什麼要比較勒?很簡單!因為所有的排序都是兩兩比較的阿!既然需要比較,那我們就應該要 implement Comparator,而且,在JDK 5.0 以後的環境,我們有了 Generic 可以使用,所以我們就設定我們要 implement Comparator<String>。那又為什麼是要用 String 呢?因為當初會排序失敗,是因為 displaytag 是直接用字串來比較排序的!所以就用 String 囉~ 很直觀吧!

我們完成的 class 如下:

public class NumberComparator implements Comparator<String>
{

    public int compare(String arg0, String arg1)
    {
        Double num1 = Double.parseDouble(arg0);
        Double num2 = Double.parseDouble(arg1);
        return num1.compareTo(num2);
    }

}

在這裡我們因為 implement Comparator interface,所以我們要實做出 compare() method,那因為我們是要作數字的比較,我們就將傳入的兩個字串parameter 先轉換成 Double (這裡不用 Integer 是怕數字的位數不夠,乾脆就一勞永逸)。

Then? 看到這裡你一定會這樣問,好像很直觀的想法到這裡就卡住了!

別急,我們還沒找過 displaytag 的 tags 與 attributes。看看 tag reference 我們就可以發現,display:column tag 中有個 attribute 為 comparator,並且從描述中就可以瞭解到此 attribute 是用來讓我們可以增加自己的 comparator,所以我們就將我們剛剛傳寫好的 NumberComparator 指定給要使用數字排序的 column。這樣就大功告成~ 收工!~

2008年9月28日 星期日

[Design-Pattern] Open-closed Principle(OCP)

Robert C. Martin 曾經說道 "Software entities should be open for extension, but closed for modification."

在設計一個模組時,應該使該模組之後可以在不被修改的前提下被擴充。i.e.不必修改程式碼的情況下使模組改變行為。

這樣的假設之下,乍看之下是不可能的,但如果借用 interface 以及 abstract class 的幫助之下,這樣的情況就有可能會實現。利用 interface 及 abstract class 定義出 method 的 signature 來作為系統中的抽象層,因此這些signature 制定了可能的擴充,i.e.在設計系統之初,就必須為系統的可擴充性加以構想,這也就使得系統中的抽象層不必再修改,i.e.關閉修改之窗。

藉由 implement 抽象層的 object 來實現具體行為,並藉由改變實體行為的模式,來達到擴充的功能,i.e.開啟擴充之窗。

如此就可以滿足"Open-closed Principle",並且具有兩大優點:

1.藉由擴充來位系統提供新的行為,以滿足要求。

2.關閉修改模組可以使系統擁有原有的穩定性。

系統在初期設計時的確很難去思考到 OCP,因為一開始系統在分析時遇到的 class 數量可能不是很多,但是隨著系統的逐步增長, class 的數量可能就會越來越多,當 class 之間的關係越來越複雜的情況下,如果不遵守 OCP,最後系統就會是一團無法擴充與維護的垃圾,因為系統中的 class 之間有著極高的 coupling 與極低的 cohesion,這會造成牽一 class 而動全系統的悲劇。

所以在系統成長到一定程度時,programmer 就要開始思考 class 之間的關聯是否會太過於緊密而失去彈性。

[Object-Oriented] Interface

在JAVA中,interface的使用對於有學過JAVA的人來說,我想一定是不陌生的。但是,確實該如何好好的使用interface的強大功能,我想沒有經驗的programmer應該是陌生的!

有相當多的書籍中,皆有提到關於Polymorphism的強大之處,但是想要真正使用到,對於沒有經驗的人來說,有點困難之處。

Polymorphism與interface之間的關連,就像是Mechanism與Policy一樣。Polymorphism是一種Mechanism,而interface就是一種實現Polymorphism的Policy。

舉個簡單的例子,假設我們要採用"防盜"的機制(Mechanism),那我們可以選擇的策略(Policy)就有很多種,我們可以選擇加裝更好的鎖來達到防盜的目的,或是我們可以請保全公司來加裝保全系統等,都是可以達到我們當初所設定的目標。所以 Mechanism 是一種目標,而 Policy 則是達成目標所可以選擇的方法。

回想起在某一本書中看到,interface定義一種data type,某個class implements interface就會擁有該種data type。然而,interface的使用,不僅僅可以讓程式變得簡潔有力,並且達到限制基本功能的能力,還可以增加系統的彈性。就像對於學生與老師之間的關連,老師接受教導學生,但是學生本身卻有上千種不同特質,所以我們可以利用interface來定義所有的學生,並且要求他們具有基本的能力,諸如:選課、翹課等。

所有想要被老師所教導的各種學生,都必須實做出學生interface所約束的基本能力,i.e.選課與翹課。老師接受一個實做學生interface的object,就可以對他的基本能力做呼叫,這樣就算是一種Polymorphism。

也就是,見人說人話,見鬼說鬼話。

interface使得系統中的Extensibility, Flexibility, Pluggability 獲得保證

2008年9月12日 星期五

[Agile Method] Pair-Programming

Pair-programming 顧名思義就是 pair-thinking 或是 pair-doing something

在台灣的公司,幾乎看不到這樣的情形,因為台灣的文化與思想都是一個人獨立思考、獨立作業。

但是,pair-programming 確實是有他的好處,當自己真的去實踐才會有體會!

在我的碩士生涯中,雖然只有短短的兩年,不過我已經自己接下兩個 CASE,一方面是為了賺取生活費,但是目的卻是在於學習。

CASE 通常都是一個人在自行開發的,我也不例外。我在實驗室中被分配到開發工業研究技術院(Industrial Technology Research Institute of Taiwan,以下簡稱 ITRI)的計畫,不過很特別的是我的指導教授 - 陳振炎教授,他特別提到說,我與我的開發夥伴 - Brian 必須一起作同一件事,也就是實行 pair-programming。

所謂的 pair-programming 就是我跟 Brian 要一起寫同一支程式,但不是各寫各的,是要一起寫,所以就是一個人負責 key-in codes 而另一個人負責檢查邏輯。現在市面的不論是付費或是免費的 IDE 都已經具備有程式 syntax 的檢查,但是邏輯的檢查卻是只能靠 programmer 自己去解決,而 pair-programming 就是在解決一個思考漏洞的問題。

實際上這很有趣,因為我們從來沒有這樣的經驗,不過我卻也深深地體會到,一個人的能力真的很有限!因為我跟 Brian 之間的實力在伯仲之間,所以我們的思考與想法都很接近,但是人與人之間一定會存有衝突!那怎麼辦?好在我們兩在進入碩士前就彼此認識,所以在溝通上我們都盡量以雙方意見的結合為最終的結果,所以一直到現在我們都很順利的在開發。

反觀我所接的 CASE,因為只有我一個人開發一個部份,所以所有的問題我只能自己想辦法自己解決,除非我真的想不出來,我才會去請教朋友。

但是問題出現在實際面上,一個企業不可能僱用兩個人去作同一件事,這對於台灣的企業來說是不符合成本的,所以現階段要讓 pair-programming 在台灣實現事很有困難的!

2008年7月29日 星期二

[Struts] StrutsTestCase for JUnit

Introduction

對於寫過test case 的 programmer 來說,JUnit 應該是不陌生,JUnit 是被大家公認用來執行java測試最好的framework,但是對於像Struts這種以MVC為宗旨的framework,我們又該如何去執行測試呢?StrutsTestCase for JUnit[1] 就是一個用來達成我們目的的solution。從字面上就可以了解,StrutsTestCase其實就是將JUnit延伸到Struts framework可以使用的library。所以,大致上對於測試的方式是大同小異的,不過對於StrutsTestCase[1]來說,還是有一些不一樣之處。

下圖一是官方網站的簡介,此網站對於StrutsTestCase[1]有很詳細的說明。

clip_image003

圖一 官方網站的說明

圖一中的紅圈處就可以下載該library。點選後就會進入SourceForge.net的網站,如下圖二。

clip_image005

圖二 SourceForge.net的下載點

接個點選Download字樣,進入如下圖三的畫面。

clip_image007

圖三 SourceForge.net的下載點(2)

畫面中請下載您所需要的版本,通常是選擇下載最新版本為主,圖中最後一個載點是在檔名後方加上 -src代表是原始碼的版本,若您有需要訂做自己適用的StrutsTestCase,您也可以下載原始碼版本。

下載完的壓縮檔案解開後的資料夾中會包含一個strutstest-x.jar(其中x會依照您所下載的版本不同而有所改變)。若是一般的 Java Application 要使用,請設定 classpath 位置;若是需要在 Web Server 上面的 Web Application 使用,請放置該 Web Application 的 WEB-INF / lib 之下。

Example

首先假設您有一個action如下:

public class LoginAction extends Action {

    public ActionForward excute(...) {

        String username = ((LoginForm)form).getUsername();

        String password = ((LoginForm)form).getPassword();

        String role = “user”;

        if (username.equals(“adm”) && password.equals(“test”)) {

            role = “adm”;

        }

        request.getSession().setAttribute(“role”,role);

        request.getSession().setAttribute(“username”,username);

    }

}

上述程式碼中,我們假設您已經有一個LoginForm class。上述的action您在struts-config.xml中設定如下:

<action path=”/login” type=”LoginAction” />

接下來您就可以根據您的action來開發strutstestcases測試,開發strutstestcase步驟如下:

(1) 您必須建立新的class,我們命名為LoginActionTest並且extends MockStrutsTestCase:

public class LoginActionTest extends MockStrutsTestCase {

}

(2) 如果您是在 eclipse 環境下執行測試,請您務必加入以下的程式碼:

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”)); //註一

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

}

註一:其中WebContent要根據您在eclipse中建立的資料夾而定,依照我所設定的資料夾如下圖四,所以我們採用WebContent:

clip_image013

圖四 Eclipse專案的目錄結構

(3) 接著我們就要開始來正式的撰寫我們的test case,所有的test case method都必須以test開頭,例如:testLogin。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {}

}

(4) 有了test case method,我們就要開始撰寫內容了。首先,我們要先告知strutstestcase我們要測試的路徑,也就是瀏覽器如何存取該action的方法,不過,我們只需要設定在struts-config.xml中的路徑即可,無須增加IP位址,因為MockStrutsTestCase是屬於Mock Test測試(也就是非in-container測試,所謂的container就是由tomcat這類的server來啟動,所以in-container就是會啟動tomcat server來進行測試,Mock Test就是另一種不用啟動tomcat server的離線測試),先前我們已經將LoginAction class在struts-config.xml中設定的路徑為/login,所以我們可以透過MockStrutsTestCase所提供的setRequestPathInfo() method來設定action的存取位置。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

    }

}

NOTE: 呼叫superclass的methods時,可以使用super關鍵字,本人建議所有呼叫superclass的methods時,都加上super關鍵字,以方便未來觀看程式碼時就可以一目了然,另外,若是呼叫同一個method時,請您也加上this來方便日後的維護。這並沒有硬性的規定,所以的關鍵字都不加上去,程式碼依舊可以執行,這只是習慣性的問題而已。

(5) 再來就是告知該action我們要傳入的資料,此資料就是模擬使用者會傳入哪些資料給action。切記,雖然我們的action是接收一個ActionForm物件,不過在MockStrutsTestCase中是用類似URL GET method(../login.do?username=adm&…)的方式給予。我們並不能給予ActionForm物件。所以我們要呼叫MockStrutsTestCase的addRequestParameter() method,並將我們要給予的參數名稱與參數值。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

    }

}

(6) 設定好路徑與參數後,我們就要告知MockStrutsTestCase,該測試可以啟動了,所以我們就呼叫他的actionPerform() method來正式的執行該action。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

        super.actionPerform();

    }

}

(7) Action執行完後,我們就可以針對action執行後的結果作測試,因為我們的action是將結果儲存在session中,所以我們就要從session中取出結果,並且呼叫JUnit的測試method來檢驗結果。

public class LoginActionTest extends MockStrutsTestCase {

    public void setUp() {

        super.setUp();

        setContextDirectory(new File(“WebContent”));

        this.setConfigFile(“/WEB-INF/struts-config.xml”);

    }

    public void testLogin() {

        super.setRequestPathInfo(“/login”);

        super.addRequestParameter(“username”,”adm”);

        super.addRequestParameter(“password”,”test”);

        super.actionPerform();

        String role = super.getSession().getAttribute(“role”);

        super.assertEquals(role,”adm”);

    }

}

NOTE:若action是將結果儲存在request中,您就要改呼叫super.getRequest() method來進行測試。

References

[1] StrutsTestCase官方網站 http://strutstestcase.sourceforge.net/

2008年6月5日 星期四

[SQL] 搜尋金額的範圍內容

資料庫真是一項好用的東西,尤其是精通 SQL 語法的話,對於 programmer 來說,真是如虎添翼阿~~

回想起大學時代修資料庫系統的課,老師第一堂課就說:所有的程式,資料的運算如果能用 SQL 解決,就一定要用 SQL 解決,不然,你花大錢買他幹嘛!!如果只是用資料庫來存資料,取資料都只有簡單的 CRUD (Create,Replace,Update and Delete),那跟直存成檔案有啥差別!!

這句話讓我深深的體會在這次的 CASE 中。因為我這次接的 CASE 是關於撥款的系統,很多資料的運算要處哩,如果全部都讓我寫程式自己作,那效能一定會是最令人詬病的!!

回到我們的主題,光看這主題一定不能了解我要解的問題??

precondition : the database is MS SQL Server 2K

那我們就來描述一下我所遇到的問題:

如果使用者給你的 table 長這樣

Table A

range ....

------------------------------------------

0~100 ...

101~200...

...

但是使用者希望他是輸入 51,然後你就要把第一筆的資料撈給他!!

剛開始我就想,哇!! 好難~~但是,問題就是要解,那要怎樣解勒??

一步步來,首先,我們要先試試看能不能把 0~100 這種字串透過 SQL 來拆解,透過線上的文件中我們可以由 String 資料型態的相關 function 值中發現一個 function - CHARINDEX(pattern, str)

於是我們就寫下

SELECT CHARINDEX('~','0~100')

結果的確是我們要的結果,CHARINDEX function 是用來將特定的 pattern 來給與 str 中的位置,由上面的測試結果為 2 就可以知道這 function 對我們是有用的~~

接著就是要取出部分的字串,這項要求很熟悉,

沒錯要用 SUBSTRING(str,begin,len),於是我們就用它來先取出左邊的範圍吧!!

DECLARE @x AS VARCHAR

SET @x='0~100'

SELECT SUBSTRING(@x, 1, CHARINDEX('~', @x)-1)

上面的作法是為了不要每次都打 '0~100' ,所以宣告一個變數來儲存~~

上面的 SQL 語法得到的結果會是 0 ,恭喜~~我們成功了左邊的範圍。

那右邊的範圍可以如法炮製嗎?? 好像還差一個東西:我們要如何知道字串的長度勒??

答案是 LEN(str) function

所以我們來整合一下吧~~

DECLARE @x AS VARCHAR

SET @x='0~100'

SELECT SUBSTRING(@x, 1, CHARINDEX('~', @x)-1) AS leftRange,

         SUBSTRING(@x, CHARINDEX('~', @x)+1, LEN(@x)) AS rightRange

完成了一半囉!!

接下來就是要將剛剛的 SQL 語法套用到我們的問題中。

SELECT SUBSTRING(range, 1, CHARINDEX('~', range)-1) AS leftRange,

SUBSTRING(range,CHARINDEX('~',range)+1,LEN(range)) AS rightRange

FROM A

結果就是

leftRange    rightRange

-----------------------------------

0                100

101             200

...

的確這樣我們就可以拆解出範圍囉~~

如果們把這樣的結果當作 subquery,對這樣的結果作進一步的包裝,那我們就可以將使用者的要求達成囉~~ 下面假這使用者輸入的內容為 @input

DECLARE @input AS VARCHAR

SET @input='51'

SELECT T.*

FROM (

  SELECT *, SUBSTRING(range, 1, CHARINDEX('~', range)-1) AS leftRange, SUBSTRING(range,CHARINDEX('~',range)+1,LEN(range)) AS rightRange  FROM A

) AS T WHERE @input BETWEEN T.leftRange AND T.rightRange

問題得到解決了~~

回到一開始,如果這樣的問題要用程式來解決的話,勢必要花掉很多的時間,所以能用 SQL 幫我們運算,何樂而不為呢~~

以上的經驗分享之~~

2008年5月7日 星期三

[Struts] 分享我的開發流程

對於 Web Programmer 來說,使用 framework 是一件既方便也是必須作的事

因為 framework 幫助我們減少開發上枯燥乏味的 details

增加我們開發 Application(Web or Alone) 的速度!!

Struts 是一個在 JAVA 環境下的 Web framework

它的主要精神就是在於 MVC (Model-View-Controller) 的結構

以往使用 JSP 開發的 programmer,為了要達到 MVC 的 pattern

往往採用 MVC Model1 - JSP + JavaBean 的使用,

或是採用 Model2 - Servlet + JSP + JavaBean

但是無論是 Model1 or Model2 最困難的問題就是 effort 太高

MVC 之間的元件需要自行管理,Request Processes 需要自行管理等...

這些繁雜瑣碎的事物,我們透過使用 framework 就可以輕鬆的幫我們解決

Struts 對於 MVC 的支援相當的完整,使用者透過向 controller 發出 request

Controller 會決定是否需要到 Model 中取得資料給 View ,或是直接顯示 View來回應使用者

MVC 的 pattern 讓 programmer 之間可以達到 separate of concern,好處有太多,大家可以慢慢的體會~~

不過一種 framework 有再多的好處,如果無法掌握其開發的 process,對於 programmer 來說,那還不如就不要用!!

接下來我就提供我歷經兩個專案後所得到的開發經驗:

MVC 的核心在於 Controller,Controller 是整個 MVC 的開端,但是 Controller 端又需要 Model 端做為 Data Supplier

所以,以我的經驗事先從 MVC 的 M 開始著手

接下來就直接開發 Model components 囉!!

等等,先別急~~

Model components 對於 programmer 來說大多是跟 Database 存取有關

所以市面上有許多 ORM 的 solutions,比如目前最有名的 Hibernate (小弟尚在研究中~~)

這些 ORM 主要就是幫助我們解決 object and relative 的 mapping

畢竟,Database 的世界與 Program 的世界還是相距甚遠~~

今天不談 ORM,所以我們就將 Model components 用 JDBC 來設計

熟悉 JDBC 的人都知道,最中取出來的資料是一個 ResultSet 物件

如果我們就直接將 ResultSet 物件丟給 Controller 去處理,這樣就需要由 Controller 負責將 Model 產生的結果作收尾的動作

因為 ResultSet 是一個持續跟 Database 有連線關係的物件,照理說應該是由 Model 端元件來負責 ResultSet 的資料清裡

所以我們就需要在 Controller and Model 之間改以 JavaBean 做為溝通的橋樑

所以我們的開發第一步就是:開發一個與 Database Table 一樣內容的 JavaBean,也就是開發一個 Entity Bean

有了 Entity Bean,第二步就是:開發我們的 Model 元件

透過撰寫 SQL 語法對 JDBC 作存取的動作,並且在查詢結果 return 之前,Model 元件需要負責關閉 JDBC 物件與 Database 之間的連線,以確保資源的釋放

有了這兩樣的元件後,直觀的來說,就是對 Controller 開始動手囉!!

不過,回到 Struts 的運作,因為使用者可能由 View 端 submit requests,所以 View and Controller 之間的溝通是透過 ActionForm 物件

ActionForm 其實算是一種 JavaBean,不過他提供更多一點的功能

所以我們的第三步就是:開發 ActionForm

開發 ActionForm 物件最主要就是需要釐清使用者會將哪些資料 submit 給 Controller

有了這三樣元件後,我們就是真的要開始開發 Struts 中最核心的 Controller

第四步:開發 Controller

完成了 Controller 的內容,我們就可以知道哪些資訊是需要給使用者觀看的,所以我們就可以開發 View 端

第五步:開發 View

View 端透過一些 tags 來完成顯示的內容

以上是我開發的習慣流程,如果您是正需要使用 Struts 來開發您的 Web Application,您可以參考這樣的開發流程,來減低您 survey 的時間

2008年5月3日 星期六

[Displaytag] Displaytag:一個可以簡單完成分頁的 library

對於 web programmer 來說,分頁的資料顯示方式確實是一項令人頭痛的問題,因為必須先支到資料內容的長度,資料分頁後的頁數,以及使用者目前瀏覽到哪一個分頁的資料等等

Displaytag

是一個可以讓 web programmer 輕鬆就完成多資料分頁功能的 open source solution

官方網站中對於自家的 library 有了一些很詳盡的說明,以下就對於分頁的功能作部分的探討

Displaytag 首先就如同其他 JSP tag 一樣,需要再使用前先宣告

<%@ taglib uri="http://displaytag.sf.net" prefix="display" %>

接著我們就是以 display 做為 Displaytag 的 namespace

簡單的分頁使用就是對 table 設定 pagesize attribute:

<display:table name="myList" pagesize="10">
</display:table>


接著就是如果我們的 framework 使用的 Struts,我們還可以讓每次換頁時,都向 controller 重新取資料,那我們就必須設定 requestURI attribute:
<display:table name="myList" pagesize="10" requestURI="/paging.do">
</display:table>


這樣就會在每次分頁時,重新跟 paging controller 要求資料

不過你一定會很好奇,那他要的資料是要部分呢? 還是全部的資料?

別以為程式會這麼聰明,每次分頁時,當然要的都是全部的資料啊!!但是它只會顯示你要看的中間幾筆資料!!

那這樣記憶體空間不就浪費了!! 沒錯~~

所以在 displaytag 中提供了兩種方式來只取部分資料

不過我只有是成功其中一種 XD

首先建立一個 class 是 implement org.displaytag.pagination.PaginatedList:
public class MyPagingList implement org.displaytag.pagination.PaginatedList {
}

裡面會有一些需要實作出來的 methods:
getFullListSize():告知前端的 displaytag 總共會有多少筆的資料,讓前端可以產生所需要的頁數

getList():讓前端的 displaytag 取資料的 method,List 裡面存放的是取出部分的資料

getObjectsPerPage():告知前端的 displaytag 一頁顯示的資料筆數

getPageNumber():告知前端的 displaytag 目前是屬於第幾頁的資料

getSearchId():讓前端的 display 用於搜尋的功能

getSortCriterion():告知前端的 displaytag 目前排序的依據

getSortDirection():告知前端的 displaytag 目前排序的方向 ASC or DESC

我們可以利用後端的 Model 來取得這些資訊,並將這些資訊給與上面我們所建立的物件,再透過 controller 來給與 view 端的 displaytag 作顯示

對於使用者來說,觀看的內容並沒有任何的差別,但是對於顯示的速度來說確實是會加快(當總資料的筆數有上萬筆來說),因為取部分資料的方式,對於記憶體來說,永遠都會只占有那幾筆的數量,在空間上來說確實是一項節省

[Struts] 建立一個可以讓使用者下載檔案的 Action

Struts 的 Action 扮演 MVC(Model-View-Controller) 中的 Controller 角色

所有 Struts 的 request 都會先經過 Controller 在進行後端的 Model 計算並產生前端的 View

相信對於使用 Struts 的 programmer 來說,一般的 Action 都是不陌生的!!

但是,要如何可以讓使用者透過瀏覽器來下載檔案呢??

當然這檔案不是一個 static file,要對使用者的 request 產生一個 dynamic file

static file要做到就是直接將路徑指到 browser 的 URL 就OK

那 dynamic file呢?

Struts 提供一種 Action - DownloadAction,就如同一般的 Action 使用一樣

但是不同點在於需要 implement 一個 getStreamInfo method

所以我的 Controller 就會長成這樣:

public class MyController extends DownloadAction {
    public StreamInfo getStreamInfo(...) {
    }
}

基本的 class 架構就如上面的樣子,那 getStreamInfo method 呢?? 我要怎樣去完成??

我們先討論為何會有 getStreamInfo method 的存在

這個 method 是讓我們可以撰寫要讓使用者下載檔案的內容

觀察 Struts 的 DownloadAction source code 就可以發現,DownloadAction 是如何執行的:

1.首先呼叫我們撰寫的 getStreamInfo() method 來建立一個 StreamInfo object

2.接著就是由 StreamInfo object 中獲得 content type and inputStream

3.再來就是設定 browser 的 content type,告知 browser 要如何處理該檔案,如果是可以由外部存取的檔案,就會試著呼叫相關的 application 來支援

4.最後就是將 inputStream 的內容 copy 到 response 的 outputStream

使用者就可以透過 browser 取得檔案或是呼叫相關的 application 來處理檔案

知道流程後,我們就必須要撰寫 getStreamInfo() method 來符合流程囉~~

由上述的流程中,我們需要建立一個 StreamInfo object,裡面包含 content type and inputStream,所以重點就在於 StreamInfo object 的撰寫

觀察 Struts API 就會發現我們竟然找不到 StreamInfo interface 的內容!! 該不會 API 出錯??

身為一個 JAVA Programmer 的直覺,StreamInfo 一定是 DownloadAction 的 inner interface,所以我們要改查 DownloadAction.StreamInfo,果然不出我所料!!

既然是一個 interface 那我們就要接著觀察他有哪些 Known Implementing Classes

在 API 中立即就寫出:
All Known Implementing Classes:
    DownloadAction.FileStreamInfo, DownloadAction.ResourceStreamInfo


所以我們就要觀察哪個 implementing class 是我們所需要的!!

從 DownloadAction.FileStreamInfo 中知道這是用來產生檔案下載的,而 DownloadAction.ResourceStreamInfo 是用來產生網路資源的

所以我們就鎖定 DownloadAction.FileStreamInfo class 來使用

決定好關鍵的 class 之後,就是開始撰寫我們 getStreamInfo() method 囉~~

然而,需要建立一個 DownloadAction.FileStreamInfo 物件就需要設定他的 content type and file object(因為 constructor 的 parameter)

所以我們就要先建立一個 file 物件來讓 DownloadAction 的 inputStream 可以完成第四項流程

我們需要將我們的 dynamic file 備份到 local 的 HD 中,再將我們存放的路徑給予 File object

所以我們的 getStreamInfo() method 會先有這樣的內容:

public StreamInfo getStreamInfo(...) {
    String path = "/a.dat";
    File file = new File(path);
}

再來就是 content type 的部分,content type 就是告知 browser 該如何去處理接著要下載的檔案,例如我們設定 application/pdf,browser 就會預設呼叫 Adobe Acrobat Reader 來讀取

但是這樣還是無法完成正確的下載,所以我們必須在 StreamInfo 物件送交之前,對 response 設定 Header 為 "Content-disposition",value 為 "attachment; filename=a.dat"

這段設定的目的在於,強制 browser 不要在 browser 本身開啟檔案,強制 browser 透過下載檔案的方式來處理,而 filename 的部分卻可以讓我們對於使用者下載的檔名給與預設值

所以最後完成的內容應該會包含這樣的樣子:
public class MyController extends DownloadAction {
    public StreamInfo getStreamInfo(...) {

        String path = "/a.dat";
        File file = new File(path);
        String contentType = "application/unknow";
        //商業邏輯...
        response.setHeader(
            "Content-disposition",
            "attachment; filename=a.dat");
        return new FileStreamInfo(contentType,file);

    }
}

這樣我們就可以完成讓使用者下載檔案的功能了!!

2008年3月13日 星期四

[Eclipse Plug-in] CodeCover : An open-source glass box testing tool

Introduction

對於測試人員來說,也許 JUnit[3] 就已經夠用了,但畢竟對於非測試人員來說,JUnit 無法滿足他們的要求,圖形化、數據化的報告是必要的。一個 open-source 的工具 CodeCover[1] 就是一項不錯的支援工具。他不僅可以像一般的 standalone application 來使用,對於 eclipse[2] IDE 也有支援的 plug-in。

本文將以安裝、使用以及產生測試報告的架構來說明 CodeCover 工具。

Install

若您是想要使用 standalone 的版本,請由下列網址下載:

http://downloads.sourceforge.net/codecover/codecover-batch-1.0.tar.bz2

若您是想要使用 eclipse[2] plug-in的版本,請遵循以下的安裝步驟:

1. 啟動您的 Eclipse[2]

2. 從功能列中選擇 [Help] -> [Software Updates] -> [Find and Install]

clip_image002

圖一:安裝 eclipse plug-in

3. 在開啟的新視窗中選擇 [Search for new features to install] 並且按下確定

clip_image005

圖二:搜尋新的plug-in

4. 接著在視窗的右邊按下 [New Remote Site]

clip_image008

圖三:新增 Remote Site

5. 在新的 New Remote Site 視窗中填入以下資訊:

               Name: CodeCover Update Site

               URL:  http://update.codecover.org/

6. 新增了新的 Remote Site 之後,原先的 Install 視窗會自動將您剛才新增的 Remote Site 勾選好,接著就是按下 [Finish]

7. 接下來的視窗會出現目前有哪些更新或是新的 plug-in 可供您安裝,請勾選 CodeCover[1] ,並按下 [Next]

8. 接下來的安裝 Eclipse[2] 也許會問您是否要安裝此元件,請您就選擇 [Install All] 來安裝全部的 plug-in

How to

1. 首先要請您先安裝 statement coverage plug-in,請參考 Install Section

2. 對於該專案要使要 statement coverage plug-in ,請記得對該專案開啟 statement coverage plug-in

對該專案按下右鍵,選擇 properties

clip_image010

圖四:開啟專案的 Properties 視窗

開啟視窗後,選擇 CodeCover[1],在右邊會出現設定,請將 Enable CodeCover 勾起,並且至少選擇一項需要用到的功能

clip_image012

圖五:對該專案開啟 CodeCover 功能

3. 再來就是要開啟對哪些 class 需要作 statement coverage,並對該package (如此會對所有的 package 底下的所有 sub-packages 都執行相同的動作)按下右鍵,將Use For Coverage Measurement 打勾

clip_image015

圖六:勾選欲使用 CodeCover 的package

4. 接著就是對某個 test code 作執行的動作,例如使用testcode.basic_data.Basic_dataTestSuit ,開啟該檔案後,在功能列表中按下 run按鈕右邊的下拉式選單

clip_image018

圖七:開啟執行的功能

接著選擇 Run As -> CodeCover Measurement For JUnit。

clip_image019

圖八:對測試檔執行 CodeCover Measurement For JUnit

5. 在等待測試後會出現 JUnit[3] 的測試視窗,告知是否有測試成功,綠色的 bar就是代表成功,而紅色的bar 就是表示測試中有出現錯誤或是與測試的期望值不符的情況,此時您就可以在錯誤的回報視窗中了解錯誤的來源。

clip_image021

圖九:JUnit 測試結果視窗

6. 接著就是開啟 CodeCover[1] 的報告視窗並將剛剛的測試結果勾起,接著就可以到 Coverage 視窗中觀看測試的百分比。

Coverage 視窗開啟方式:[window] -> [show view] -> [other...] -> [CodeCover]之下選擇 [Coverage] 就可以開啟。

clip_image023

圖十:選擇 CodeCover 測試結果報告

您可以透過觀看所有 class 來方便觀察,或是您希望使用哪種方式觀看,在coverage 視窗右上角的按鈕可以選擇。

clip_image026

圖十一:觀看 CodeCover 的測試結果

Create a report

1. 經由 Eclipse[2] 來產生 CodeCover[1] 測試過後的測試報告,請您先選擇功能列中的 [File] -> [Export]

clip_image028

圖十二:產生測試報告的匯出步驟

2. 在 Export 視窗中,請您選擇 [CodeCover] 階層之下的 [Coverage Result Export] 並按下 [Next]

clip_image031

圖十三:選擇 Coverage Result Export

3. 在 [Export Wizard] 視窗中選擇您剛才所測試過的測試結果,在下方的 [Export Type and Destination] 中的 [Type] 請選擇 [Report],並且選擇您想要匯出的目的地的檔案(預設的檔案格式為 *.xml),並按下 [Next]

clip_image033

圖十四:選擇欲匯出的測試結果、測試報告的形式以及測試報告的目的地

4. 在下一個視窗畫面中,您必須選擇您的 [Report Template] (請您先下載 CodeCover 的 standalone 版本,並且在解壓縮後的資料夾內您會有 /report-templates/,若您想要 export 為 HTML 樣式,請選擇 HTML_Report_Hierarchic.xml),按下 [Next] 之後就會開始產生結果報告

clip_image035

圖十五:選擇測試報告的 Report Template

5. 在產生完結果報告後,請您到您方才指定的匯出位置觀看報告結果。產生的結果報告會以資料夾的方式出現,請您選擇資料夾中任一個index.html,並且以瀏覽器開啟該檔案,報告會以階層式的排列來產生報告。

clip_image037

圖十六:測試報告的HTML形式範例

Reference

[1] CodeCover 官方網站 http://codecover.org/index.html

[2] Eclipse IDE 官方網站 http://www.eclipse.org/

[3] JUnit 官方網站 http://www.junit.org/

2008年3月12日 星期三

[iText] iText : A free java-PDF library

Introduction

iText 是一個可以讓Java program 產生 PDF 檔案的 library。其安裝的方式如下列步驟:

1) 經由 iText 官方網站[1]下載此 library,如下圖一

請選擇左邊的 [Download iText](圖一藍圈處) -> [Compiled Code]

clip_image004

圖一 官方下載的網頁

2) 下載的 iText-2.0.8.jar (後面的版本編號可能有所不同),若是一般的 Java Application 要使用,請設定 classpath 位置;若是需要在 Web Server 上面的 Web Application 使用,請放置該 Web Application 的 WEB-INF / lib 之下。

Example

我們在此展現一個將 iText 使用在 Struts framework 上的範例。

(1) 首先就是要先建立一個 PDFAction extends Action

public class PDFAction extends Action
{
    public ActionForward execute(...)  throws Exception
    {}
}

(2)再來就是要建立一個 Document 物件,此物件是 iText library 中最核心的物件,因為他會幫我們展現我們最後的 PDF 內容。

Document document = new Document(PageSize.A4, 50, 50, 30, 30);

(3)建立完核心的物件後,最重要的就是設定該 document 的輸出,一般的 application 就可以直接設定成檔案的輸出,不過在網頁中的輸出卻是 user 的 browser,所以輸出的設定也是一項考驗。

ByteArrayOutputStream out = new ByteArrayOutputStream();
PdfWriter.getInstance(document, out);
document.open();

//PDF Wrinting begin=================================

//PDF Wrinting end==================================

document.close();
response.setContentType("application/pdf");
ServletOutputStream sout = response.getOutputStream();
out.writeTo(sout);
sout.flush();
sout.close();
return null;

首先我們要建立一個 ByteArrayOutputStream 物件,他會負責幫我們將結果輸出成 ByteArray 的形式(類似 row data),並且我們要將 document 的輸出串流指定成剛剛的 ByteArrayOutputStream,接著就是啟動 document 物件。

設定好撰寫內容的起始設定後,當我們內容撰寫完後,我們也必須設定一些物件的狀態。當然,第一步就是關閉我們的 document 物件的寫入,這就當之後有程式碼要對他在進行寫入的動作時,系統會 throw an exception 給我們。另外,告知 browser 我們所要給他的文件格式也是很重要的,所以,我們就設定 response 物件的 content type 為 application/pdf。

接下來就是比較神奇的設定,我們要從 response 中獲得一個 output stream,並且將他指定為 ServletOutputStream,因為 ServletOutputStream 物件可以將我們的 PDF 內容轉換成 Binary data 傳送給 client 端,並且,我們要將先前建立的 ByteArrayOutputStream 的寫出導到 ServletOutputStream,最後就是將 stream 給 flush 並且關閉他。Action 的 execute method 最後回傳的 ActionForward 物件必須為 null,因為我們不需要在做任何的轉換,也不需要在將 controller (也就是此 Action) 的控制權轉交給某個頁面,因為我們已經將輸出的 content type and content 給設定好,所以是回傳 null 的 ActionForward。

(3)再來就是我們要輸出的 PDF 內容囉!從上述的程式碼,我們撰寫 PDF 的內容是在兩段 comments 之間。另外,在官方網站中的範例,他的程式碼都是 run 在單機上,不過對於 PDF 內容的撰寫卻可以移植到我們的 action 中,也就是接下來,您可以參考官方網站的範例程式,將 PDF 撰寫的程式碼放在上面程式碼中的 comments 之間,就可以執行了。當然,Struts 的基本設定是少不了的,在此就不再贅述了~

References

[1] iText 官方網站 http://www.lowagie.com/iText/

該網站提供所有相關於 iText 的所有資訊

[2] iText API http://itext.ugent.be/library/api/

[3] iText Tutorial : iText by Example http://itextdocs.lowagie.com/tutorial/

該網站提供許多線上的基本範例,除了提供範例程式外,還提供程式產生 PDF 的結果以供參考

2008年3月11日 星期二

[Java] JUnit 3.8.1 : An Unit Test Framework for Java

Introduction

對於 programmers 來說,如何確保自己的程式碼是正確的,通常都必須要自己親自去執行程式碼,然後自己扮演使用者的角色來手動測試,這已經是早期的 programmers 會做的是了。JUnit 就是一套 framework,他幫助 programmers 解決測試中繁雜的工作,透過撰寫 unit test code 來達到測試的效果,並且採用自動化、可重複測試的功能來降低 programmers 的工作。

clip_image002

圖一 JUnit 官方網頁

在 Eclipse 中,JUnit 是核新的 plug-in,除非您將他反安裝了,否則您可以在 Eclipse 中找到。

Example

要撰寫一個 unit test code 其實是一件很簡單的事,因為 JUnit library 都已經將繁雜的工作改以簡短的程式碼來解決。

(1) 首先,我們要先建立一個 JUnit 的 class,點選 eclipse 中的 [File] -> [New] -> [Others],接著您就會看到如圖二的畫面出現。

clip_image004

圖二 建立新的元件視窗

(2) 點選該視窗的 [JUnit Test Case],並且會出現如圖三的畫面。其中我們選擇上方的 [New JUnit 3 test],並且我們將我們的 unit test case class name 填入 name 欄位,接著選擇下方的 [Class under test] 右方的 [Browse…] 按鈕,接著會出現視窗讓我們選擇我們想要測試的 class。

clip_image006

圖三 建立新的 JUnit Test Case

(3) 選擇好上述的資訊並填寫完畢後,按下 [Next>] 後會出現如圖四的視窗。我們可以由此視窗中選擇我們對於該 class 中我們想要測試的 methods,並將他打勾,eclipse 會自動幫我們產生測試的基本程式框架,以方便我們測試。按下 [Finish] 後就會開啟測是的程式碼,我們會看到剛才我們所勾選的 methods 會以 testXXX() 的方式出現。

NOTE:本文件示範的 JUnit 版本為 JUnit 3,若您有興趣可以自行研究 JUnit 4,不過要先對於 JDK 5.0 的 Annotation 有基本的概念才行。

clip_image008

圖四 選擇目標 class 我們想要測試的 methods

(4) 有了程式的框架,我們當然還是要自己撰寫測試的 scenarios。首先我們從新假設我們要測試的目標 class 內容如下:

public class Math {

    public int add(int x, int y) { return x + y; }

    public int subtract(int x, int y) { return x - y; }

}

(5) 接著我們就用上述的 eclipse 工具幫我們產生測試程式碼的框架:

public class MathTest extends TestCase {

    public void setUp() { super.setUp(); }

    public void testAdd() {}

    public void testSubtract() {}

}

上述程式碼中的 setup() method 是用於起始設定的 method,也就是 JUnit 在呼叫每個測試 methods 前都會先執行 setup() method,如上面的測試程式碼中,JUnit 會依照下面的順序執行測試:setUp(), testAdd(), setUp(), testSubtract()。如果您的測試中是需要先建利一些物件,如:資料庫的連線 class等,您就不必在每個測試 methods 中撰寫建立這些物件的程式碼,您可以統一撰寫在 setup() method 中。

您所有的測試 methods 都必須以 test作為開頭,因為 JUnit 測試會根據 method 前面為 test 開頭的來進行測試。

(6) 接著我們就要開始撰寫測試的 scenario,在此我們就只示範 testAdd() method。

public class MathTest extends TestCase {

    public void setUp() { super.setUp(); }

    public void testAdd() {

        int x = 10;

        int y = 50;

        int expected = 60;

        int result = this.math.add(x, y);

        super.assertEquals(result, expected);

    }

}

上述程式碼中,我們先宣告一個class內部變數 math他是我們測試的目標,並且我們在 setup() method 中將他初始化。接著假設我們要輸入的 x and y 的值,以及我們所期望的輸出結果 expected 變數,然後我們就呼叫 Math 的 add() method 來獲得結果並存在 result 變數中,最後我們就呼叫 super class 中的 assertEquals 來斷定結果一定是要相同。

(7) 再來就是執行我們的測試案例,選擇 [Run] -> [Run As] -> [JUnit Test]。測試結果會如圖五。

clip_image013

圖五 測試結果報告視窗

如果您的測試無誤的話,就會在右邊出現綠色的 bar,左邊上方有一些測試的結果資訊,紀錄著您總共測試的 methods、測試中出現的 Errors 與測試中測試與您結果不符合的 Failures。

References

[1] JUnit 官方網站 http://junit.sourceforge.net/junit3.8.1/index.html

[2] Eclipse 官方網站 http://www.eclipse.org

2008年2月9日 星期六

[Struts] Struts 的 TAG 與 javascript 互動

在Struts 中的 TAG 例如: 等,須要跟 javascript互動時,又碰巧變數儲存在 jsp scope 中,那我們該如何去解決勒??

這個問題看似簡單

<html : link action="test" onclick="javascript : return confirm( ' <%= i %>' )" />

你以為這樣就解決嗎??

上面的結果就會出現一個 javascript confirm box 上面顯示 <%= i %>的訊息

剛開始我以為這樣是一定可以成功,結果出來卻不是我要的XD

因為透過 struts tag 裡面的 scriptlets(<%=%>) 是不會被JSP 先解析,所以就想說那就把呼叫confirm 所給的 arguments 的單括號拿掉!!

<html : link action="test" onclick="javascript : return confirm( <%= i %>)" />

結果就是連 confirm box都彈不出來, 透過 firefox 的 javascript console 來觀察錯誤在哪,結果就是跟你說 < 符號不能理解XD

這問題困擾了很多天,有一天透過 google 大神終於找到解答了!!!

這解答果然有撇步~~~

只要將上面的 TAG 改為

<html : link action="test" onclick= ' <%= "return confirm( ' " +i+ " ' )" %>' />

也就是將呼叫 javascript 的 code 在 scriptlets 中當作字串來處理

然後有牽涉到 JSP scope 中變數的部分就利用 string 相加的方式完成整句在client browser 先由 server 解析

與大家分享之~~

[Struts] Struts 解決中文問題

其實Struts 已經用了一陣子了!!當初自己在開發時,遇到最大的問題就是中文的問題!!

好在 LAB 有學長也是有碰過類似的經驗,大家討論之下終於有了答案... Filter

在每個 Struts 的請求中先透過 Filter 來作編碼的動作:

package filter.encoding;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;

public class EncodingServlet extends HttpServlet implements Filter
{
    private FilterConfig filterConfig;

    //Handle the passed-in FilterConfig
    public void init(FilterConfig filterConfig) throws ServletException
    {
        this.filterConfig = filterConfig;
    }

    //Process the request/response pair
    public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain)
    {
        try
        {
            request.setCharacterEncoding("utf-8");
            filterChain.doFilter(request, response);
        }
        catch(ServletException sx)
        {
            filterConfig.getServletContext().log(sx.getMessage());
        }
        catch(IOException iox)
        {
            filterConfig.getServletContext().log(iox.getMessage());
        }
    }

    //Clean up resources
    public void destroy()
    {
    }
}

然後在 web.xml 中加入

<filter>
    <filter-name>EncodingServlet</filter-name>
    <filter-class>filter.encoding.EncodingServlet</filter-class>
</filter>
<filter-mapping>
    <filter-name>EncodingServlet</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

就是讓所有對 Controller 發出 request 的請求都能夠先經過我們的 filter

網頁之間的編碼問題解決了!!

事情還沒結束勒~~~

因為 Struts 有一種 i18n 的機制,就是讓所有 Message 都包裝在一個 ApplicationResource.properties 之中

這個檔案裡面不能將中文很正確的在 view 中顯示,所以我們需要一個工具來輔助:property editor

下載的網址如下:
http://propedit.sourceforge.jp/index_en.html

因為我是用 Eclipse 來開發的,所以我是用他對於 Eclipse 的 plug-in

安裝方式網頁上有說,不是用下載的方式,而是可以透過 Eclipse 的安裝 feature 工具來 support

當你重新開啟 Eclipse 後,對於 *.properties 檔案按下右鍵選擇 [Open with] -> [PropertiesEditor]

就可以利用這個工具來編輯 *.properties 檔案

當然它看起來就跟一般 Eclipse 內建的工具差不多,只是如果你是輸入非英文字母,它會自動幫你轉成 UTF-8 的編碼方式

另外還有一點要注意,在 view 的編碼也要改成 UTF-8 不然,我也不知道會怎樣XD

最近找到有 support struts 在 eclipse 上的 plug-in ,可是似乎都不太好用~~

但是有一個工具也可以提供給大家:Struts Console

網址如下:
http://www.jamesholmes.com/struts/console/

他也是有支援 eclipse 讓我在開發時對於編輯 struts-config.xml 來說就不是只是看一堆 XML TAG 了

他的工具也支援 Validator and Tiles 的 struts plug-in

不過對大的缺點就是對於 struts 與支援到 1.2的版本,殘念!!

剩下對於支援 Struts 的工具就有限了!!

如果要開新的 struts project ,我個人是建議直接將 struts-blank.war 匯入後在去更改內容

因為這樣是最快的方法了!! 很多東西要重新撰寫很累人!!!

希望對正要開始開發 struts 的 programmer 來說會有一些些的幫助

P.S. 還是要感謝涼鳥學長在Struts 方面給的指導!!

[Struts] Struts的 標籤使用javascript來顯示

找了好久,怕網頁不見,乾脆就先記錄下來囉!!

原網址:http://www.mail-archive.com/struts-user@jakarta.apache.org/msg69201.html


Re: <html:errors> tag in javascript alert

Martin Fekete
Thu, 12 Jun 2003 23:29:54 -0700

try this ...

errors.header =alert(
errors.prefix ="
errors.suffix ="+ "\\n" +
errors.footer ="");\n

...
<script>
function load() {
<html:errors>
}

</script>
...
<body onload="load()">
...

it should work but it won't because of
http://issues.apache.org/bugzilla/show_bug.cgi?id=17418
so you need to hack org.apache.struts.taglib.html.ErrorsTag

Feky

P.S. 感謝涼鳥學長的幫忙!!