2009年2月16日 星期一

[Struts2] 將物件作為 Action property

在之前我們所示範的範例中,Action 的 property 都是以 primary data type 為主!如果我們有一些 domain object 已經定義好了,那我們可不可以 reuse 這些 objects 呢?以 programmer 的角度來說,應該是可行!沒錯~ Struts2 的確有提供這樣的機制,讓我們可以 reuse 已經定義好的物件!例如:我們已經定義好一個 User 物件如下:

public class User
{
private String name;
private String password;

public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

User class 中包含了 username 與 password 的 properties,當然,我們的物件也一定是一個 JavaBean,這樣我們才可以針對物件的 properties 進行存取!

接下來,我們就要更改我們的 HelloUser Action 的程式,當然是要使用我們已經定義好的 User 物件作為 property。在 Struts2 中提供了兩種方式:Object-backed JavaBean properties 與 ModelDriven actions。接下來我們會分別介紹這兩種方式來達到我們的目的。

Object-backed JavaBean properties

Object-backed JavaBean properties 的方式是最直接的,就是直接將我們所定義好的 User 物件當作 HelloUser Action 中的 property,所以我們的 HelloUser 程式修改為:

package silver8250.hello;

import com.opensymphony.xwork2.ActionSupport;

public class HelloUser extends ActionSupport
{
private User user;
private String message;

public User getUser()
{
return user;
}
public void setUser(User user)
{
this.user = user;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
public String execute()
{
this.message = "Hi~"+this.user.getName();
return super.SUCCESS;
}
}

上面的程式碼中只是很簡單的將我們 User 物件當作 property,而且我們不必自己去初始化這個物件,因為 Struts2 framework 會幫我們去處理這個細節。這樣的方式讓我們的 Action 看起來更簡潔有力了~

不過對應的 Jsp 頁面則是要作一些修正,因為我們原先將使用者輸入的資料是直接屬於 Action 之下,但是因為我們現在是將使用者輸入資料的對應 property 放至於 User 物件中,所以我們要將 tag 更改為:

<s:textfield name="user.name" label="Your Name" value="" />

很直覺吧~當然,取值的時候也是這樣~其實就是 OGNL 的更改罷了~不過現在我們並不會深入 OGNL 的功能上!

ModelDriven actions

ModelDriven actions 是另一種可以實現與 Object-backed JavaBean properties 的方法,屬於 Struts2 framework 內建的機制,也就是將我們的 Action implements ModelDriven,並且 ModelDriven interface 有提供 Generic 的機制,所以我們的程式可以修改如下:

package silver8250.hello;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class HelloUser extends ActionSupport implements ModelDriven<User>
{
private User user = new User();
private String message;

public User getModel()
{
return user;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
public String execute()
{
this.message = "Hi~"+this.user.getName();
return super.SUCCESS;
}
}

這種方式並不難,只是將我們原先針對 User 物件的 getter/setter 拿掉,並且實做出 getModel() method,而且回傳我們的 User 物件就可以了!不過這裡有一點要注意,我們的 User 物件一定要初始化,否則會在執行 execute() method 時出現錯誤!

使用 ModelDriven actions 的方式,User 物件裡面的所有 properties 就會像是被嵌入到我們的 Action 一樣,也就是說,當我們在 JSP 頁面取值的時候,OGNL 就不用像 Object-backed JavaBean properties 需要先以我們的 User 物件為開始點,所以我們可以直接取 name properties:

不過採用 ModelDriven actions 時還有一點要注意,如果我們所 reuse 的物件中與 Action 中有一樣的 property,那 Action 中的 property 就會被隱藏~例如:我們將 HelloUser Action 更改為:

package silver8250.hello;

import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class HelloUser extends ActionSupport implements ModelDriven<User>
{
private User user = new User();
private String name;

public User getModel()
{
return user;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String execute()
{
this.name = "Hi~"+this.user.getName();
return super.SUCCESS;
}
}

使用者所輸入的 name 在送出後,我們取 name 的值就會只有取到 User 中的 name,而且使用者所輸入的 name 資料也只會被存在 User 的 name 中!所以這點要小心,否則當出現問題時會很不容易發現!

Conclusion

雖然 Struts2 有兩種方式可以讓我們 reuse 已經撰寫好的物件,不過我個人是比較喜歡也比較建議使用 Object-backed JavaBean properties 的方式,因為這種方式比較直覺,而且我們可以在 Action 中 reuse 一個以上的物件,而 ModelDriven actions 的方式就不行!而且在撰寫頁面中的 OGNL 時也比較好 debug~畢竟他很明確的指出是在往下一層!

不過還是看個人習慣囉~

2009年2月15日 星期日

[Struts2] 深入實做 Action

初啼試聲 HelloStruts2 中我們已經簡單的實作出一個 HelloUser 的 Action。Struts2 中的 Action 可以簡單到只是一個 POJO,但是我們也可以將我們的 Action 藉由 implement 或 extends 一些 Struts2 內建的 interfaces 或 class 撰寫出更方便、更具有 powerful 的 Actions。最基本的 Action 就是不必與任何的 interfaces 或 class 有牽連,不過,以下將會介紹一個 interface 與一個 class,讓 programmer 在撰寫 Action 時可以更方便!

Implements Action interface

Action interface 是 Struts2 中內建的,讓 programmer 強制去撰寫出 execute() method,畢竟 Action 是一定要有 execute() method 的!不過這個 interface 不只是這樣,還提供了五個內建的控制字串,我們可以藉由回傳這些內建的控制字串,讓 Struts2 可以幫我們作智慧型的預設控制!不過,我們在設定檔中的 <result> 還是要有對應的 name 才行喔!假設我們的 Action 中 execute() method 如下:



public String execute()
{
return INPUT;
}

那我們就要在設定檔中有對應的 result tag 的 name="input" 才會有作用!



<result name="input">/index.jsp</result>

Note:Struts2 的 Action 不一定要實做 Action interface,實做 Action interface 卻可以提醒粗心的 programmer 去撰寫 execute() method!

另外,如果我們只是要負責將網址作轉換的 Action,例如我們不希望在網站中直接利用頁面的 URL 作為連結,想要透過 Action 來對應到一個頁面,我們可以不必費心的撰寫出一個空的 Action,我們只要在設定檔中加入一個 <action> tag,並且對應到一個沒有 name 的 <reuslt> tag 就可以囉!



<action name="IndexPage">
<result>/index.jsp</result>
</action>

上面的設定是利用一個空的 Action 名稱為 IndexPage 來對應到我們的 index.jsp 頁面。所以我們在網站中要對應一個 index.jsp 的連結,我們可以不必用絕對的 URL,反而可以使用 IndexPage.action 來對應,這樣的好處是當我們在變更 index.jsp 的 URL 時(假設首頁要改到別頁去),我們可以不用大費周章的修改每一個有撰寫 <a href="index.jsp"> 的頁面,反而因為我們是撰寫 <a href="/IndexPage.action"> 使得我們只要改設定檔的對應就好了!

Extends ActionSupport class

另一個很方便的 class 是 ActionSupport class,這個 class 提供了更多的功能,也幫一般在撰寫 Action 的 programmer 自動的 implements 我們可能會用到的 interfaces,如:上面所提到的 Action interface、用於驗證的 Validateable interfaceValidationAware interface 等等。並且幫我們實做出這些 interfaces 的 abstract method,所以說 ActionSuppert class 可以算是一種 Default Adapter pattern。

對於初學 Struts2 的 programmer 來說,ActionSupprt class 可以說是很方便也很常用的 class,不過,既然是繼承的關係,當然就會帶有一些缺點囉~不過我們也不是一定要繼承 ActionSupport 啦~

以下將分兩個部份介紹 ActionSupport class 所提供的功能。

1. Basic validation

剛才有說過 ActionSupport class 有實做出 Validateable 與 ValidationAware interfaces,所以 ActionSupport class 也會有幫我們實做出預設的 validate() method,不過內容當然是空的!也就是說,我們可以透過 override validate() method 來撰寫我們自己的驗證程式。



public void validate()
{
if (GenericValidator.isBlankOrNull(this.name))
{
super.addFieldError("name", "Name is required!");
}
}

上面的驗證程式是針對我們的 HelloUser 所撰寫的!用來驗證使用者在我們的 index.jsp 頁面中是否有輸入非空白的字串,所以我們採用 Commons 的 Validator framework,很幸運的 Struts2 的 Validation framework 就是採用 Commons 的!所以我們無須在下載多餘的 library 就可以使用了!另外,我們在驗證出現不合法的情況下呼叫 ValidationAware interface 的 addFieldError() method (),根據 addFieldError() method 的 parameter 說明就可以知道,第一個 parameter 是要告知 Struts2 哪一個欄位有錯誤,這樣當回到原先輸入的頁面時,Struts2 會自動在該欄位附近顯示我們的第二個 parameter 訊息給使用者!

有一點要特別的注意,因為我們使用了 validate() method 來驗證使用者輸入的值,所以我們在設定檔案中一定要有 <result name="input"> 的設定,不然當我們的驗證程式找到錯誤就回不到原來的頁面了!

P.S. GenericValidator 提供了很多很好用也很常用的驗證程式,例如我們所使用的 isBlankOrNull 就是驗證是否為空白或是 null!

說了這麼多關於驗證的東西,那這個 validate() method 到底是誰在驅動呢?回想一下我們在 How Struts2 works 中有提到 interceptor 這個東西吧~他會在我們 Action 被執行前就會先執行 interceptor stack,所以確實是有一個 workflow 的 interceptor 在幫我們呼叫 validate() method,不過我們在之後才會在深入的探討 interceptor。

2. Using ResourceBundles for message text

回到上面的程式碼中,如果你像上面的程式碼一樣直接將錯誤的訊息寫在程式碼裡的話,想必你一定會為日後的維護工作感到煩心~如果哪天主管要你加入英文版本、中文版本的訊息!你一定會因為這樣的苦差事感到心有餘而力不足阿!為了避免這樣的事情發生,Struts2 framework 有支援 I18N(Internationalization) 與 L10N(localization)的機制。很幸運的 ActionSupport class 有實做了兩個 interfaces 是負責這兩種機制的:TextProviderLocaleProvider interfaces。

TextProvider interface 是可以讓我們很有彈性的取得我們的訊息,也就是藉由設定在 ResourceBundle 中的 Key-value pattern 來取得,我們透過呼叫 TextProvider 的 getText() method 並給予一個 key 來得到 value。

如果我們將我們在 HelloUser class 中的 validate() method 更改為:



public void validate()
{
if (GenericValidator.isBlankOrNull(this.name))
{
super.addFieldError("name", super.getText("error.name"));
}
}

那我們就可以放置一個與 HelloUser class 同一個位置的 HelloUser.properties 檔案,如下圖中 HelloUser.properties 檔案放置的路徑就為 /silver8250/hello/。

並且設定我們要作 I18N 的訊息:



error.name=請輸入您的姓名

如果我們在 Eclipse 中編輯像上面 properties 檔案中的非英文文字,那我們就可以參考 Struts 解決中文問題 中提到的 PropertyEditor 這樣的 Eclipse plug-in。

LocaleProvider interface 則是提供了 getLocale() method 讓我們可以根據瀏覽器中的不同語系設定來轉換在頁面上所顯示的文字。

在此對於 I18N 與 L10N 並不會提到太深入,因為在之後我們對針對這樣的議題進行討論~

對於 Struts2 中的 Action,我們可以看到 Struts2 framework 中提供了兩種很方便的 interface 與 class 來幫助我們撰寫 Action,也提供預設的五種控制字串的功能,另外,我們探討了驗證的機制與多國語系的機制。

2009年2月14日 星期六

[Struts2] 組織 Actions

雖然說 Action 是 Struts2 中核心的元件,但是沒有了 struts-config.xml,這些我們辛苦撰寫的 Actions 也是英雄無用武之地~在 初啼試聲 HelloStruts2 中,我們定義了一個 struts-config.xml 來告知 Struts2:我們的 Actions 要怎樣去使用。而且這些 Actions 都會被組織到一個 logical container 叫做 package 之中。Struts2 中的 package 觀念其實跟 JAVA 的 package 觀念很類似,他們都是提供一種機制讓 programmers 可以很方便的組織與規劃各個 Actions 之間的群組,我們可以利用這樣的機制並根據 Actions 所提供的功能來加以分群組織。例如,我們可以將我們的網站分為:使用者登入登出、使用者瀏覽與使用者下訂單等等,每組的功能可以包裝成一個 package,而這一個 package 中又有一些 Actions 提供核心的功能,然後我們在藉由 struts.xml 來組織我們的 packages。換句話說,在 Struts2 中 struts.xml 是最上層的 package,而我們可以在 struts.xml 中加入其他我們另外定義的 packages(也是一個符合struts-2.0.dtd 的 .xml 檔案)。

<package> tag 探討

在 configuration 檔案中,主要用來組織 Actions 的就是 <package> tag,在 package tag 之下,我們利用 <action> tag 來對應到我們所撰寫的 Action。以下就是 package tag 可以設定的 attributes:

Attribute Description

name
(必須)

package 的 name,可以作為其他 packages 繼承之用
namespace package 中的所有 actions 所對應到的 namespace
extends 此 package 所繼承的其他 package 名稱
abstract 如果設定為 true,則此 package 只能被用來繼承,package 中不能有任何的 Actions 的設定

由上面的表格中我們可以了解到 package tag 中的四種 attributes,讓我們回顧一下當初在 初啼試聲 HelloStruts2 中所撰寫的 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>
<constant name="struts.devMode" value="true" />
<package name="hello" extends="struts-default">
<action name="Hello" class="silver8250.hello.HelloUser">
<result name="success">/WEB-INF/pages/hello_user.jsp</result>
</action>
</package>
</struts>

每一個設定檔案都是要以 <struts> tag 作為 root node,這也是 W3C 對於 well-form xml 的定義,接下來我們對於 package 給予一個 name 為 hello,他是繼承 struts-default,主要是在 Struts2 中所提供的 struts-default.xml 檔案(你可以在 struts2-core-[verson].jar 中找到,或是參考官方網站的資料),而 namespace 則是設定在這個 package 之下的所有 Actions 被呼叫時的上層 URL,舉個例子,假設我們的 package 設定為<package name="Test" namespace="/Hello/Struts">,然後我們有一個 Action 為 HelloUser,當我們要透過 URL 呼叫 HelloUser 這個 Action 時,我們就要輸入: http://localhost:8080/TestProject/Hello/Struts/HelloUser.action ,(我們的 web server 是跑在 port 為 8080 上,並且我們的 web app 名稱為 TestProject),這樣你應該可以更瞭解 namespace 所代表的意義。如果說我們像上面的 struts.xml 檔案一樣沒有設定 namespace 的話,就會是預設的設定值,也就是 namespace=""。最後一項 attribute 就是 abstract,這個 attribute 只能給予 boolean value(true / false),如果我們設定 abstract="true",那就意味著我們現在的設定檔的 package tag 中不能包含任何的 <action> tag,也就是不能加入 Actions,這樣的設計是來自於 Java 的 Abstract class 概念,如果我們沒有設定,則預設值是 abstract="false"。

struts.xml 探討

既然 struts.xml 是最上層的設定檔,而且我們又可以自己設定其他的設定檔案,那就意味著 Struts2 有提供特別的功能來支援,沒錯!<include> tag 就是被設計用來支援這樣的功能。在官方文件中很生動的說明了這種功能 - divide and conquer,學過演算法的應該可以瞭解這個精神!這個 include tag 是隸屬於 struts tag 之下的:



<struts>
<include file="/silver/test/other.xml"/>
</struts>

從上面的設定檔中我們加入了一個 include tag,而 include tag 只有一個 attribute 為 file,是用來指定該設定檔的,如上面所撰寫的代表有一個 other.xml 檔案(符合 struts-2.0.dtd 的規則)並且被放置於 /silver/test/ 目錄之下,也就是放在 silver.test 的 Java package 之下。利用這樣的方式,我們的網站就可以根據不同組的功能來分別開發,最後再到 struts.xml 檔案中作結合。

[Struts2] 簡介 Struts2 的 Action

初啼試聲 HelloStruts2 中我們可以清楚的了解到 Action 是 Struts2 framework 中最核心的角色!接下來我將會介紹 Action 所提供的三個最主要的功能,並且與 Struts1 的 Action 作比較。

在 Struts2 中的 Action 主要有三件事要作。首先,你應該已經很清楚了,既然 Action 是 Struts2 framework 中最核心的角色,那一定會負責 business logic 的工作。第二,Action 會將使用者在頁面上所輸入的資料帶入 Action 中,如此一來,我們的 Action 才可以根據使用者所輸入的資料執行我們的 business logic。最後一項工作就是,Action 會負責哪些頁面是要回應給使用者的,也就是當 Action 執行完成 business logic 後,會將結果的顯示頁面回應給使用者。

以下將分成三點加以介紹。

Actions 包裝 business logic 的工作

Actions 既然是核心元件,自然就會包裝了整個網站的 business logic,所以我們可以在 Action 中所提供的 execute() method 裡加入我們的 business logic。



public String execute()
{
this.setName("Hello~"+this.getName());
return "success";
}

上面的程式碼是根據 初啼試聲 HelloStruts2 中所介紹的 HelloUser class。範例中的程式碼是簡單的,如果我們所開發的網站中,所撰寫的 business logic 是很複雜的,我們可以不必完全的撰寫在 Action 中的 execute() method 中,因為這樣反而會造成程式碼在維護上得困難!

我們可以將我們的 business logic 進一步的包裝成 components,在將這些 components 注入(inject)到 Action 中,透過這樣的 dependency injection 的方式讓 Actions 與 business logics 之間的相依性(dependency)與耦合度(coupling)降低。甚至,我們可以使用 Spring framework 來幫助我們整合管理這些注入的元件。

回顧 Struts 1.x V.S. Struts 2.x 中提到,Action 的 execute() method 在這兩個版本是有很大的差異,總歸一句,Struts 1.x 的 execute() method 相對於 Struts 2.x 來說是比較繁瑣、肥大與測試上不方便,不過如果你寫過 Struts 1.x 的 Action 對於 Struts 2 Action 中提供的 execute() method 可能會出現小小的疑惑:那我要怎樣取得 HttpServletRequest 與 HttpServletResponse 物件呢?一開始我也會有這樣的疑惑,不過等你看到 interceptor 就會得到解答了!

Actions 負責資料的傳送

初啼試聲 HelloStruts2 中可以知道,Action 中會帶有使用者中所輸入的資料,所以 Action 是需要提供一群 getter 與 setter 來讓資料可以存取。不過,資料的帶入真的是由 Action 來完成嗎?其實不是,Action 只是資料帶入的終點與資料帶出的起點,真正幕後的執行者是 interceptor 在做,也就是說,Action 中的那些 JavaBean properties 會在使用者輸入資料送出後,被某一個 interceptor 負責將使用者輸入的值帶入到 Action 中的 properties。當 Action 完成整個 business logic 的工作後,這個 interceptor 也會負責將 Action 中的值帶出到使用者的頁面上。

不同 Struts 1.x 所使用的 ActionForm 來負責接收使用的資料,Struts 2.x 中省去了 ActionForm 的角色,讓這些工作都交由 Action 來負責,這樣看似讓整個 Action 變的更複雜,其實卻不然!也因為這樣的設計,Action 的 execute() method 中的 business logic 可以直接取得使用者所輸入的資料,這樣也讓 execute() method 可以不必將資料透過 parameter 來注入,使 execute() method 更加的乾淨!

Actions 負責回傳結果頁面的控制字串

Action 的最後一項工作就是:回傳頁面的控制字串。從上面的程式碼中可以發現,execute() method 的最後一行是回傳一個 success 的字串,這個字串是用來告知 Struts2 說:幫我回傳給使用者我定義在 struts.xml 中 success 所代表的頁面。當然,我們可以不只有一個頁面控制字串,在 struts.xml 中我可以定義很多個控制字串,這樣也可以讓我們很容易的維護我們系統,換句話說,當我們有某個控制字串所對應的頁面更改了,我們無須去更動程式碼,只要更改 struts.xml 中的設定就可以了!

回傳了控制字串後,Struts2 會幫我們轉換使用者的頁面,在透過剛剛說過的某個 interceptor 將 Action 中的資料帶出到頁面中!

[Software Testing] About software testing

第一次有老師在中央資工開了軟體測試這門課,如果對於軟體測試這領域有接觸過的話,相信你對於劉龍龍教授並不陌生,台灣應該就屬他是首席吧~以下是我修完這堂課後所寫的報告,內容如果有問題歡迎大家留言提出喔~

軟體測試的中心原則就是:證明程式有錯誤。所以我們要假定該程式有一個錯誤E,並且給予一組輸入值為i,期望的輸出值為o,當我們將i輸入給程式後所得到的實際輸出為o’,若o與o’不相等,我們就可以大聲的說,這程式有E這樣的錯誤存在;否則,我們就不表示任何意見。不表示意見不代表程式沒有E這樣的錯誤,而是我們的輸入值並不會讓程式產生E這樣的錯誤。

軟體開發的生命週期中,最有名也是最基本的方法是waterfall,也就是將軟體的開發分為需求、分析、設計、開發、測試與維護。在軟體測試中也有一個V-Model,他是一個waterfall model的一種延伸,在V-Model中,軟體生命週期中分為需求分析、系統設計、架構設計、模組設計與撰寫程式,並且將各個階段加入對應的測試階段,分別為驗收測試、系統測試、整合測試與單元測試。當我們在需求分析階段時,我們就要針對需求文件來設計驗收測試文件;當我們在系統設計時,我們就要撰寫出系統測試文件,並且以此類推。當我們撰寫完成我們的程式後,我們就要依序執行單元測試、整合測試、系統測試與驗收測試。也就是測試文件是與軟體的生命週期平行的開發,而測試動作則是反向來執行。

軟體測試的最小單元就是單元測試,而單元測試又可以根據輸入資料的Boundary Value進行測試,因為每個function都會有input data range,也就是有max與min的值,我們可以測試{min-1,min,min+1,max-1,max,max+1}的集合來測試function對於邊界值是否會產生錯誤。接下來我們可以將input data採用equivalence class testing,將input data作分類,將結果類似的區分成同一個class,藉由這樣的方法來找出屬於equivalence class中的值來進行Normal與Robust的equivalence class testing。我們可以在透過equivalence class進行decision table testing,decision table可以讓我們將複雜的邏輯結構化,並且可以在設計test case時作為參考的依據,所以透過decision table讓我們可以檢查出equivalence class切割時的缺陷。這些測試的方法主要是black box testing,因為我們不考慮程式內部的邏輯,只是針對input data來進行測試;相反地,white box testing就是特別針對程式內部的邏輯進行測試,所以我們可以對程式可能執行的path進行測試,這種就是path testing,藉由path的trace,我們可以找出程式中的branch或loop的coverage,也可以觀察出程式的複雜性。有了path testing,我們要在詳細的根據dataflow進行測試,觀察某些變數在某些path中的變化,對於最簡單的測試就是在程式碼中加入列印出某些變數的目前狀況,進而找出程式中data在經過複雜變化後所產生的bug。

藉由上述的unit testing,我們不僅針對功能本身的black box testing,也針對了程式內部進行white box testing。Unit是程式中最小的單元,若每一個unit都完成了測試,系統中的各個unit就必須進行整合,而針對整合的測試就是integration testing。在Integration testing因為我們不可能將所有的unit同時都進行整合,所以,測試整合的tree中,我們將被unit呼叫的假程式稱為stub,而呼叫unit的假程式稱為driver,整合主要有三種測試的方式,第一是Decomposition-based integration,由source code來得到系統功能之間的decomposition tree,然後由某一個source開始進行top-down或bottom up的測試,也可以結合top-down與bottom-up兩種的sandwich等。第二種則是Call graph-based integration,從tree中進行測試,pair-wise integration是一次針對一對的unit進行測試,這樣的測試可以使用到最少的stubs/drivers,不過缺點是整合的次數較多,當系統中的unit數量很多時,兩兩測試的次數就會增加,而另一種Call graph-based integration是neighborhood integration,根據某個source將所有的鄰居都加入測試,這種方式可以不需要任何的stub/driver。第三種的整合測試方式為Path-based integration,根據program tree進行整合測試。當整合測試中有發現任何unit之間存在問題,就必須修改,修改完成後就要進行回歸測試(Regression testing),回歸測試就是將被修改的unit與他相關的units進行一次整合測試,然後系統在整體的進行整合測試,這樣才能保證被修改的unit不會影響到相關的units與系統中的所有units。

整合測試完成後,最後就是系統測試,所謂的系統就是只軟體與硬體,所以系統測試就是將我們所開發的軟體佈署到硬體上進行測試,因為軟體在開發機器上可以執行,不代表在正式機氣上是可以執行的,所以要進行軟硬體的系統測試。確定軟體可以在硬體上正確的執行後,我們就要將系統交付給客戶,因為客戶是最終測試者,也就是要進行驗收測試,在驗收測試中,客戶需要針對需求中的內容一一進行驗收。

以上就是根據V-Model中測試的生命週期,不過隨著程式語言的方法演進,傳統的Structure Programming Language上的測試方法對於新的Object-oriented programming language就不見得完全有用,所以針對OO的測試方式有些方面是需要修改的。傳統的程式對於unit的定義在於一個最小的可執行component,或是由同一個開發者所撰寫的component。對於OO來說,unit則是Class,這樣的優點是對於test case的定義可以更加的明確,以使得整合測試時目標更明確(Classes 之間的整合),不過OO測試所存在的缺點為大多數的測試工作都轉移到整合測試上,原先傳統的整合測試是針對components之間的測試,而OO中可能有很多的Classes才會組成一個component,也就是OO中的unit更小了。另外,OO中提供了inheritance的特性,繼承讓程式可以很輕易的reuse,不過對於測試來說也就更加的棘手,所以針對繼承結構中的subclass,我們可以採用Flattened class方式,將該subclass所繼承的所有super classes的fields與methods都寫入該subclass中,也就是將class壓扁平,這一樣就可以只針對此subclass進行測試。對於OO來說,測試的階層會因為unit的定義不同而有不一樣的階層結構:若是以class為unit,則就會與傳統的測試階層類似;若是以method為unit,則測試階層會在多了一個level,最下層就是unit testing,但是因為methods是存在class中,所以在unit testing與integration testing之間就會多了class testing。

根據測試的生命週期所發展的測試方法有很多種。Exploratory Testing是一種測試的方式,藉由測試者本身對於問題的瞭解(也就是domain knowledge)、對於程式的技巧與經驗來進行測試,這種測試方法有點像是口試一樣,測試者是口試官,而被測試的程式則是面試者,測試者根據本身的經驗來設計可能的bug存在的test case並測試該程式。這種測試方式對於測試者本身的要求很高,因為測試者需要對於domain有一定的知識與經驗,否則無法從很刁鑽的角度找到程式的問題,而且測試者需要對於程式抱持著好奇心與創造力,去思考一些程式中沒有考慮到的問題。不過對於較複雜的系統就不太適用,因為系統若很龐大,測試者很難去全面的考慮,所以針對小的系統來說,這種測試方式比較有效。

Model-based Testing是一種針對系統中的Model進行測試。複雜的系統通常很難測試,如果測試系統的model就會讓問題簡化,以利於測試的執行!首先將系統進行modeling,把複雜的部份去除而不失真,接著就是找出一條系統的執行過程但是不考慮太多的例外情況,並將這樣的執行過程撰寫成test case在進行測試。

在Extreme programming(XP)中提出的Test-driven Development(TDD)是一種很不一樣的測試方式,要求programmer先根據scenario撰寫出test case,有了test code在撰寫source code。這樣的測試方式打破了傳統的思維:先有test code再有source code。不過這樣的測試方法有一個盲點,programmer針對test case來寫source code,這樣的source code並不保證在test case修改後還能夠執行,也就是source code會陷於滿足test case的盲點。不過,我們實驗室是提倡agile method,所以在計畫的開發上也有採用TDD,就我自己本身對於TDD的看法,我覺得source code會陷於這樣的盲點會是當初客戶給的scenario本身就存在盲點,若是scenario在描述使用者的動作有全面的考慮到,test cases就不會只有侷限在某些情況下,當然source code也就不會受到侷限。在TDD的使用上,我個人覺得很不一樣,以往都是先針對scenario去思考系統的層面,藉由TDD的先撰寫test cases,讓programmer可以先根據使用者的角度去思考我要怎樣用系統,這樣對於scenario的內容就必須更深入的思考,而且當source code完成後,programmer往往會懶得去撰寫test code,測試上就會有盲點,而測試的方式都是以執行看看在找問題,這種方式讓programmer在測試的角度就不太客觀,所以TDD將programmer的思考角度反轉,先知道使用者真正的使用角度再去撰寫程式。