(2005.11月號-142期)_CanooWebTest實作(下)
在上一期的內容中我們簡單的介紹了WebTest,並且使用了Maven協同WebTest進行簡易腳本測試,而本期將更深入的探討其腳本製作上的考量與進行模組化的腳本製作。
序言
經過上一期的的說明,相信讀者對WebTest已經有了進一步的認識,但在眾多的自訂標籤中如何適當的運用來協助進行測試,才是重要的課題。接下來筆者將進行進階版本的測試案例撰寫,並且重覆進行改寫,藉以循序漸進的導引讀者了解WebTest於實務上應用的特性,以利於未來在實際應用上以模組化、彈性化、容易維護的角度進行測試案例的撰寫。
註:本次範例請參考資料結構1,完整程式碼請至RunPC網站下載。
資料結構1
Jpetstore_WebTest
+--webtest 腳本檔資料夾
| +--config 客製化屬性放置路徑
| | +--login.properties
| | +--shopping.properties
| +--modules 測試用模組放置路徑
| | +--checkout.ent
| | +--login.ent
| | +--logout.ent
| | +--set.init.ent
| | +--set.config.ent
| | +--shopping.ent
| +--testsuit1 測試案例1
| | +--login.xml
| | +--newAccount.xml
| | +--webtest.xml
| +--testsuit2 測試案例2
| | +--shopping_dogs.properties
| | +--shopping_fish.properties
| | +--shopping.xml
| | +--webtest.xml
| +--testsuit3 測試案例3
| | +--login_shopping_checkout.xml
| | +--webtest.xml
| +--webtest.xml
+--project.properties
+--project.xml
測試案例一
在上一期的實作中,當webtest.xml腳本檔撰寫完成之後,即可透過maven webtest進行功能測試,這是因為在Webtest Plug-in中預設執行路徑指定在${basedir}/webtest下的*.xml檔的緣故,因此若將撰寫完成的腳本檔置於${basedir}/webtest的資料夾中,無需指定腳本檔名WebTest即可進行所有腳本的測試。
而從另一方面來說,以這種方式將無法指定腳本執行順序,雖然我們可以指定maven.webtest.file=Test*.xml執行特定腳本檔,但顯然彈性應用上還是不夠,因此比較建議的方式為建立一個只包含執行順序內容的腳本檔,並在執行時指定該腳本為功能測試的進入點。撰寫方式參考腳本1,在本期的所有測試案例中都將指定webtest.xml做為腳本執行順序檔,例如腳本1即依順序執行了,新增帳號與登入這二個測試腳本。
就目前所知預設的JPetStore的範例中並未建立帳號相關資訊,在此我們使用WebTest進行建立帳號的測試腳本,參考腳本2可知所有的腳本檔中至少必須包含二個實體檔供XML腳本使用ENTITY的方式進行引入,分別是webtestmodulesset.init.ent與webtestmodulesset.config.ent,這二個檔案在上一期中已經介紹過,在此不再贅述。
腳本2第12行<testSpec>標籤是WebTest的測試進入點,這個標籤中必須包含了<config>標籤的網站連結設定與<steps>標籤的測試步驟。在此要注意的是每一個testSpec標籤都擁有各自的Session,不同的testSpec區塊內容都定義者在不同Session內的動作。
腳本2第15行<invoke>標籤則是透過URL的方式進行網頁請求的連結,若invoke的網頁不存在則會發生StepFailedException,對於配置基礎驗證(Basic Authentication)的網站,也可透過username與password屬性設定進行登入,url則代表欲連結網頁的相對路徑。
腳本2第24行<repeat>標籤代表著重覆次數的操作,在本範例中我們進行了10次的帳號建立動作,經由27行invoke將網頁導至新增帳號表單,並透過<setinputfield>標籤對指定的欄位設值,在33行中可以發現引用了EL語法#{count}取得目前已重覆次數作為帳號名稱的區隔,這種寫法跟程式for(int i=0 ; i<10 ; i++)中取得i的做法是相同的,預設是透過#{count}取得重覆次數。
腳本2第41行<clickbutton>標籤則是模擬了滑鼠按下Submit按鈕的動作,將表單資料送出。當所需欄位資料輸入完成送出的同時,系統將建立該帳號並自動進行登入。
腳本2第44行<verifyxpath>標籤,在說明該標籤之前,我們知道帳號已經建立並且登入,而如何確定真的登入了,這時就該判斷回應的網頁上是否出現了所需的訊息,例如使用者未登入之前,網頁上出現會有[登入]的連結,已登入時則有[登出]的連結。若這些訊息只是單純的字串時我們可使用<verifytext>來驗證,如<verifytext stepid="驗證是否登入" text="登出" />,但若驗證訊息若不是文字的話,verifytext就無法進行檢查了。在本例中登入與登出的連結皆是以圖片顯示,這時就必需使用XPath協助對HTML結構進行查詢。在本例中<verifyxpath stepid="確認是否已登入" xpath="//img[@src='../images/sign-out.gif']"/>意謂著驗證網頁中是否包含了IMG並且src屬性指向../images/sign-out.gif圖片的物件。讀者若對XPath有興趣可參考http://www.w3.org/TR/xpath官方網站或至http://www.purpletech.com/xpe/index.jsp下載XPath Explorer進行XML Path Language的學習。
到目前為止我們已經說明了測試案例一所需標籤的使用,而測試流程的建立則必須由測試人員進行實際的系統操作並評估於何處加入檢查點,以了解系統是否如預期的回應。測試人員可一邊進行系統操作,一邊進行腳本建立,建立腳本所需的資訊如連結網址、填入欄位名稱、點選按鈕屬性等都是由使用者端的網頁得來的,有時無可避免測試人員必須檢視HTML原始碼以了解更多的訊息,但畢竟都比白箱測試來得簡單多了。
測試案例一撰寫完成後於Console下執行maven webtest -Dmaven.webtest.file=testsuit1/webtest.xml進行測試與報表的建立。
腳本1 testsuit1webtest.xml
<?xml version="1.0" encoding="Big5"?>
<project name="WebTest" basedir="." default="all">
<target name="all" >
<ant antfile="newAccount.xml" dir="."/>
<ant antfile="login.xml" dir="."/>
</target>
</project>
腳本2 testsuit1newAccount.xml
01 <?xml version="1.0" encoding="Big5"?>
02 <!DOCTYPE project [
03 <!ENTITY init SYSTEM "../modules/set.init.ent">
04 <!ENTITY config SYSTEM "../modules/set.config.ent">
05 ]>
06 <project name="newAccount" basedir="." default="all">
07 &init;
08 <target name="all" >
09 <antcall target="newAccountTest"/>
10 </target>
11 <target name="newAccountTest" >
12 <testSpec name="newAccountTest" >
13 &config;
14 <steps>
15 <invoke
16 stepid="連結「JPetstore首頁」網址"
17 url="shop/index.do"/>
18 <invoke
19 stepid="連結「登錄」網頁"
20 url="shop/signonForm.do"/>
21 <invoke
22 stepid="連結「新增帳號」網頁"
23 url="shop/newAccount.do"/>
24 <repeat
25 stepid="新增指定人數帳號"
26 count="10">
27 <invoke
28 stepid="連結「新增帳號」網頁"
29 url="shop/newAccount.do"/>
30 <setinputfield
31 stepid="輸入User ID"
32 name="account.username"
33 value="TestID#{count}" />
34 <setinputfield
35 stepid="輸入New password"
36 name="account.password"
37 value="0000" />
38
39 ...中間省略...
40
41 <clickbutton
42 stepid="按[Submit]送出"
43 name="submit"/>
44 <verifyxpath
45 stepid="確認是否已登入"
46 xpath="//img[@src='../images/sign-out.gif']"/>
47 <invoke
48 stepid="進行登出"
49 url="shop/signoff.do"/>
50 </repeat>
51 </steps>
52 </testSpec>
53 </target>
54 </project>
測試案例二
測試案例二中,我們將進行購物車的操作,在尚未進行腳本撰寫之前先行於系統上進行寵物商品的導覽,確定要購買的分類(Category)、產品(Product)、項目(Item),並製作如測試參數1與測試參數2的屬性檔。
接著撰寫腳本4並在testsuit2webtest.xml中置入測試參數檔進行腳本的執行,在腳本3中我們可以發現本次的測試進行了二次測試腳本的執行,並分別以shopping_fish.properties、shopping_dogs.properties進行測試參數的設定。腳本4第22行<clicklink>標籤模擬按下Link的動作,並在該檔中進行了${category.id}、${product.id}、${item.id} 取得ANT的Property,因此在每一次進行shopping.xml腳本載入的同時會依據給予的properties檔的不同,設予不同的測試參數,在此相同的測試腳本就能重覆的利用。
但這樣的寫法在實際的執行上卻發生了問題,我們進行了二次購物車的動作並購買了商品, 照理來說應該在購物車上顯示二項商品才是,不過在這裏無論進行了多少次的購物動作,購買的商品都只有最後一項,前面購買的商品都會被忽略。
請參考腳本4第12行,筆者在前面曾經提過每一個testSpec標籤都擁有各自的Session,不同的testSpec區塊內容都定義者在不同Session內的動作,這就表示了在webtest.xml中進行的二次腳本,是屬於二次的購買動作,並不屬於一次的操作情節。
因此讀者在實作的同時必須注意,腳本檔中可以撰寫多個不同的testSpec,但每一個testSpec都是獨立的測試情節,所有的操作是不可分割的。另外使用ANT的Property並不適合作為測試參數使用,因為在ANT中Property一旦設定了值,就無法再次變更,這種特性作為動態參數的傳遞並不恰當。
若要執行測試案例二,可於Console下達maven webtest -Dmaven.webtest.file=testsuit2/webtest.xml指令,筆者將在測試案例三進行改寫。
腳本3 testsuit2webtest.xml
<?xml version="1.0" encoding="Big5"?>
<project name="WebTest" basedir="." default="all">
<target name="all" >
<ant antfile="shopping.xml" dir=".">
<property file="shopping_fish.properties"/>
</ant>
<ant antfile="shopping.xml" dir=".">
<property file="shopping_dogs.properties"/>
</ant>
</target>
</project>
腳本4 testsuit2shopping.xml
01<?xml version="1.0" encoding="Big5"?>
02<!DOCTYPE project [
03<!ENTITY init SYSTEM "../modules/set.init.ent">
04<!ENTITY config SYSTEM "../modules/set.config.ent">
05]>
06<project name="shopping" basedir="." default="all">
07 &init;
08 <target name="all" >
09 <antcall target="shopping" />
10 </target>
11 <target name="shopping" >
12 <testSpec name="shopping" >
13 &config;
14 <steps>
15 <invoke
16 stepid="連結「JPetstore首頁」網址"
17 url="shop/index.do"/>
18 <invoke
19 stepid="瀏覽選購,連結「${category.id}」網頁"
20 url="shop/viewCategory.do?categoryId=${category.id}"
21 />
22 <clicklink stepid="點選Product"
23 label="${product.id}"
24 />
25 <clicklink stepid="點選Item"
26 label="${item.id}"
27 />
28 <invoke
29 stepid="點選[${item.id}]加入購物車"
30 url="shop/addItemToCart.do?workingItemId=${item.id}"
31 />
32 </steps>
33 </testSpec>
34 </target>
35</project>
測試參數1 testsuit2shopping_dogs.properties
category.id=DOGS
product.id=K9-DL-01
item.id= EST-10
測試參數2 testsuit2shopping_fish.properties
category.id=FISH
product.id=FI-FW-02
item.id=EST-21
測試案例三
參考腳本6,在測試案例三中,我們將購物流程抽離獨立成單一的實體檔modulesshopping.ent,在腳本5進行實體檔引入的動作並透過configshopping.properties進行參數的設值,參考測試參數3。
腳本5第25~30行<storeDynamicProperty>標籤,有別於ANT的Property的設定,使用該標籤的參數,可以在腳本檔中重覆定義值而達到動態設定參數進行測試的目的,當使用storeDynamicProperty設定參數後取得動態參數值則必須使用#{ }EL語法,請注意與ANT內定的${ }語法的不同。
除了購物動作之外,在本例中也將登入(login)、登出(logout)、付款(checkout)的動作抽離出成單一實體模組,以供未來進行新增測試案例時重覆使用,因此我們在腳本5中完成了一個完整的Scenario。請參考腳本7、8、9與圖1。
測試案例三撰寫完成後於Console下執行maven webtest -Dmaven.webtest.file=testsuit1/webtest.xml即可完成整個Scenario的測試。
腳本5 testsuit3login_shopping_checkout.xml
01<?xml version="1.0" encoding="Big5"?>
02<!DOCTYPE project [
03<!ENTITY init SYSTEM "../modules/set.init.ent">
04<!ENTITY config SYSTEM "../modules/set.config.ent">
05<!ENTITY login SYSTEM "../modules/login.ent">
06<!ENTITY shopping SYSTEM "../modules/shopping.ent">
07<!ENTITY checkout SYSTEM "../modules/checkout.ent">
08<!ENTITY logout SYSTEM "../modules/logout.ent">
09]>
10<project name="login_shopping_checkout"
11 basedir="." default="all">
12 <property file="../config/login.properties"/>
13 <property file="../config/shopping.properties"/>
14 &init;
15 <target name="all" >
16 <antcall target="login_shopping_checkout" />
17 </target>
18 <target name="login_shopping_checkout" >
19 <testSpec
20 name="測試情節:1登入2購物3付款4登出" >
21 &config;
22 <steps>
23 &login;
24 <group stepid="進行購物">
25 <storeDynamicProperty
26 name="category.id" value="${category01.id}"/>
27 <storeDynamicProperty
28 name="product.id" value="${product01.id}"/>
29 <storeDynamicProperty
30 name="item.id" value="${item01.id}"/>
31 &shopping;
32 <storeDynamicProperty
33 name="category.id" value="${category02.id}"/>
34 <storeDynamicProperty
35 name="product.id" value="${product02.id}"/>
36 <storeDynamicProperty
37 name="item.id" value="${item02.id}"/>
38 &shopping;
39 ...中間省略...
40 </group>
41 &checkout;
42 &logout;
43 </steps>
44 </testSpec>
45 </target>
46</project>
腳本6 modulesshopping.ent
<?xml version="1.0" encoding="Big5"?>
<invoke
stepid="連結「JPetstore首頁」網址"
url="shop/index.do"/>
<invoke
stepid="進行瀏覽並選購,連結「#{category.id}」網頁"
url="shop/viewCategory.do?categoryId=#{category.id}"
/>
<clicklink stepid="點選Product"
label="#{product.id}"
/>
<clicklink stepid="點選Item"
label="#{item.id}"
/>
<invoke
stepid="點選[#{item.id}]加入購物車"
url="shop/addItemToCart.do?workingItemId=#{item.id}"
/>
測試參數3 configshopping.properties
category01.id=FISH
product01.id=FI-FW-02
item01.id=EST-21
category02.id=DOGS
product02.id=K9-DL-01
item02.id=EST-10
category03.id=CATS
product03.id=FL-DSH-01
item03.id=EST-14
category04.id=REPTILES
product04.id=RP-SN-01
item04.id=EST-12
腳本7 moduleslogin.ent
<?xml version="1.0" encoding="Big5"?>
<group stepid="使用者登入">
<invoke
stepid="連結「JPetstore首頁」網址"
url="shop/index.do"/>
<invoke
stepid="連結「登錄」網頁"
url="shop/signonForm.do"/>
<setinputfield
stepid="輸入Username"
name="username"
value="${username}" />
<setinputfield
stepid="輸入Password"
name="password"
value="${password}" />
<clickbutton
stepid="按[Submit]送出"
name="update"/>
<verifyxpath
stepid="確認是否已登入"
xpath="//img[@src='../images/sign-out.gif']"/>
</group>
腳本8 moduleslogout.ent
<?xml version="1.0" encoding="Big5"?>
<group stepid="登出">
<invoke
stepid="進行登出"
url="shop/signoff.do"/>
<verifyxpath
stepid="確認是否已登出"
xpath="//img[@src='../images/sign-in.gif']"/>
</group>
腳本9 modulescheckout.ent
<?xml version="1.0" encoding="Big5"?>
<group stepid="付款">
<invoke
stepid="連結「付款」網址"
url="shop/checkout.do"/>
<invoke
stepid="查看[Checkout Summary]並按下[Continue]"
url="shop/newOrder.do"/>
<invoke
stepid="進行付款"
url="shop/newOrder.do?_finish=true"/>
<verifytext stepid="確認是否出現訂購完成訊息"
text="Thank you, your order has been submitted" />
</group>
圖1 測試案例順序圖。
結語
在這二期的文章中,筆者所介紹的都是以自動化測試為主的工具。讀者心中可能會有所疑惑:
1.自動化測試是否真的能夠為軟體開發帶來實質的效益嗎?
2.自動化測試是否能完全取代人工測試?
3.目前的商業測試工具多數都有「錄製/播放」的功能,對於需要撰寫腳本檔的測試工具並沒有實務上的用途。
第一點的答案是肯定的關於「軟體測試自動化」已經被許多的軟體測試專家驗證是可行的,並且反覆的運用在許多軟體開發過程中。大多數參與軟體測試的專家也同意自動化測試不只是值得的同時也是必要的。但在建構自動化測試的之前,必須備有一套「正規」的人工測試流程。並且從商業功能規格或設計文件中擷取必要的測試案例與建立可獨立的測試環境。再者必須了解到不是所有的測試都適合或值得自動化。例如將過度複雜的測試自動化可能會花費更多的成本。請謹記,將自動化的重點放在主要的測試個案上。只有會重複執行的測試才需要自動化。對於只做一次的測試能免則免。
自動化測試最實際的應用與目的是自動化回歸測試。也就是說,我們必須要保存所有測試個案的結果,而且這些測試個案是可以重複執行於每次應用軟體被變更後,以確保應用軟體的變更沒有產生任何因為不小心所造成的影響。
而對於第二點來說自動化測試並不會取代人工測試或是縮減測試人員的數量。自動化測試工具就只是工具,幫助測試人員可以將軟體測試工作做得更好。自動化測試工具可以幫測試人員執行那些無聊,但是又必須一直重複執行的測試個案。讓測試人員有更多時間去設計更好、更能有效找到問題的測試個案。自動化測試主要意義在於利用策略、工具以及產出等,減少人工介入非技術性 ( unskilled )、重複性 ( repetitive )、冗長 ( redundant ) 的測試動作。
最後須要帶給各位的觀念是雖然多數的商業測試工具都有「錄製/播放」測試案例的功能,但以「錄製/播放」的方式實施自動化儘量能免則免,原因在於基本上透過錄製建立的腳本,裡面的資料都可能是硬編碼,而當應用軟體資料變動、軟體功能變動,意味這些硬編碼的資料可能也需要修改,甚至必須重新錄製腳本。並且維護這些錄製的腳本,成本是非常高的,對於軟體開發與測試來說不可不謹慎。
沒有留言:
張貼留言