Selenium 是一種測試框架,它使您可在 Web 應用程序上輕松地運行用戶驗收測試(user acceptance test)。本月,Andrew Glover 將向您展示如何以編程的方式運行 Selenium 測試,并使用 TestNG 作為測試驅動程序。在將 TestNG 靈活的測試特性(包括參數化 fixture)添加到 Selenium 固有的工具包后,您需要做的就是借助 DbUnit 和 Cargo 的幫助編寫完全自動化、邏輯可重復的驗收測試。
Selenium 是一種 Web 測試框架,它搭建了驗證 Web 應用程序的新途徑。與大多數嘗試模擬 HTTP 請求的 Web 測試工具不同,Selenium 執行 Web 測試時,就仿佛它本身就是瀏覽器。當運行自動的 Selenium 測試時,該框架將啟動一個瀏覽器,并通過測試中描述的步驟實際驅動瀏覽器,用戶將使用這種方式與應用程序交互。
由于開發人員和非開發人員都能夠使用 Selenium 輕松地編寫測試,使得它從眾多測試框架應用程序中脫穎而出。在 Selenium 中,可以通過編程的方式編寫測試,或者使用 Fit 樣式的表,并且編寫了測試后,可以使測試完全自動化。使用一個 Ant 構件(比方說)運行完整的 Selenium 套件非常簡單,并且還可以在持續集成(Continuous Integration,CI)環境中運行 Selenium 測試。
這個月,我將介紹 Selenium,并逐一查看使它成為優秀 Web 測試框架的一些特性 —— 尤其是在結合使用 TestNG、DbUnit 和 Cargo 這樣的軟件時。
![]() |
|
在 Selenium 中,您可以使用自己喜愛的語言或者 Fit 樣式的表通過編程來編寫測試。從測試的角度來說,不管使用什么語言,測試過程和結果都不會有顯著的差別。在此,我希望研究 Selenium 的編程方法,因為在結合使用 TestNG 時,它提供了一些有趣的可行方法能性。
使用具有類似 TestNG 這樣的框架的 Selenium 進行編程式測試具有這樣一個優點,它允許您創建智能 fixture,而使用 Fit 樣式的表則很難做到這一點。TestNG 尤其適合與 Selenium 結合使用,因為它使您能夠完成其他框架無法做到的測試,例如使用依賴項進行測試,重新運行失敗了的測試,以及使用單獨文件中定義的參數進行參數化測試。所有這些特性結合在一起,當然能夠使它在眾多 Web 應用程序測試框架中脫穎而出,但是,正如您將看到的,在完全自動化的驗收測試中使用這些特性令它更加出眾。
Selenium 架構實際上由兩個邏輯實體組成:您編寫的代碼以及能夠簡化與測試中的應用程序的交互的 Selenium 服務器。要成功地執行測試,必須要啟動并運行 Selenium 服務器實例以及要測試的應用程序。(當然,測試結果取決于您編寫的應用程序是否優秀?。?/P>
幸運的是,Selenium 服務器是一種輕量級程序,可以在實際的測試范圍內通過編程啟動和停止它。Selenium 服務器(使用 Selenium
對象嵌入)的啟動和停止由一個 fixture 來執行。
要通過編程的方式啟動 Selenium 服務器,必須創建一個新的 Selenium
對象,并告訴它要使用哪一種兼容的瀏覽器 —— 我在下面的示例中使用的是 Firefox。您還必須提供運行服務器實例的位置(通常是 localhost
,但不是必須的),以及被測試的應用程序使用的基 URL。
在清單 1 中,我配置了一個本地 Selenium
實例,使用它在本地安裝的 Web 應用程序上驅動 Firefox(http://localhost:8080/gt15/
)。正如您從參數中推斷的一樣,Selenium
是作為被測試的應用程序的代理,并相應地促進測試。
Selenium driver = new DefaultSelenium("localhost", SeleniumServer.getDefaultPort(), "*firefox", "http://localhost:8080/gt15/");driver.start();//go to web pages and do stuff...driver.stop(); |
創建了 Selenium
實例后,您可以 啟動
并在運行時 停止
它。這意味著您可以通過編程與 Selenium 服務器交互,并通過一個測試程序使它驅動瀏覽器。
![]() ![]() |
![]()
|
通過編程與 Web 頁面進行交互是一種使用本地 id 的應用。(一些讀者可能對這種源自 本系列二月份關于 TestNG-Abbot 的文章 的概念比較熟悉)。與頁面元素進行交互的第一步就是查找該元素,通??梢允褂?HTML 元素 ID 進行查找。Selenium 還允許您使用 XPath、正則表達式,甚至是 JavaScript 來查找特定的元素(如果您希望這樣做)。
清單 2 所示的 HTML 是使用 Groovlet 的簡單 Web 應用程序的一部分。這段代碼定義了包含輸入和提交按鈕的表單。如果希望 Selenium 與該表單交互,我必須為輸入按鈕提供 ID 以及相應的值。我還需要為提交按鈕提供一個 ID,這樣 Selenium 才能 “單擊” 它。單擊按鈕后,表單將被提交給 Groovlet —— 本例中為 FindWidget.groovy
。
<form method=post action="./FindWidget.groovy"> <table border="0" style="border-style: dotted"> <tr> <td class="heading">Widget:</td> <td class="value"><input type="text" name="widget"></td> </tr> <tr> <td></td> <td class="value"><input type="submit" value="Find Description" name="submit"></td> </tr> </table></form> |
現在就可以通過使用 ID widget
(輸入值)和 submit
(單擊按鈕)與該 HTML 表單進行編程式交互,如清單 3 所示:
driver.type("widget", "pg98-01"); driver.click("submit");driver.waitForPageToLoad("10000");//assert some return value... |
Selenium 中用于和 Web 頁面元素進行交互的 API 非常的直觀。對于輸入字段,我可以使用 type()
方法將值與 ID 關聯起來。如果需要的話,可以通過編程 click
按鈕。在清單 3 中,我將 click
設置為 10 秒的等待時間 —— 足夠表單提交請求完成處理。當 FindWidget.groovy
中的代碼運行其內容并返回響應后,我可以使用該響應來查找特定頁面元素,并驗證所有內容是否正常工作。
![]() ![]() |
![]()
|
TestNG 以其靈活性和參數化 fixture 成為定義 Selenium 的驅動驗收測試的首選。TestNG 能夠定義測試依賴項并返回失敗的測試,以及其易用性,使得 Selenium-TestNG 成為吸引人的組合。
讓我們首先從一個能夠允許用戶創建、查找、更新或刪除小部件的 Web 應用程序開始。創建一個小部件需要三個屬性:名稱、類型和定義。圖 1 顯示了創建小部件的表單:
請注意:表單元素的類型是具有三個不同選項的下拉列表,如圖 2 所示:
單擊 Create Widget 將促使 Groovlet 處理這一請求。如果所有內容正確的話(即名字和定義不為空,并且數據庫中不存在該實例),Groovlet 將創建一個新的小部件實例并類似圖 3 所示的狀態頁面:
結合使用 Selenium 和 TestNG 驗證簡單的 Create Widget 用例是一種可管理的應用:
請注意:用例中的每一步都是通過 Selenium 完成的 —— 所以說,TestNG 僅僅幫助進行查找?,F在,我們來實踐一下。
![]() ![]() |
![]()
|
我希望對 Selenium 服務器進行靈活的配置,所以我將編寫一個參數化 fixture(TestNG-Selenium 樣式),一般可以使用它來為不同瀏覽器、不同位置甚至混合的 Web 應用程序地址(類似 localhost
和產品)創建 Selenium 服務器。清單 4 定義了我所配置的靈活的 Selenium 服務器 fixture:
@Parameters({"selen-svr-addr","brwsr-path","aut-addr"}) @BeforeClass private void init(String selenSrvrAddr, String bpath, String appPath) throws Exception { driver = new DefaultSelenium(selenSrvrAddr, SeleniumServer.getDefaultPort(), bpath, appPath); driver.start(); } //.... @AfterClass private void stop() throws Exception { driver.stop(); } |
必須將參數名與 TestNG 的 testng.xml 文件中的值鏈接起來;因此,我定義了如清單 5 所示的三個參數。(默認情況下為 Firefox 定義了 brwsr-path
參數,但是我可以同樣輕松地定義一組新的使用 Internet Explorer 的測試。)
<parameter name="selen-svr-addr" value="localhost"/> <parameter name="aut-addr" value="http://localhost:8080/gt15/"/> <parameter name="brwsr-path" value="*firefox"/> |
接下來,我將定義清單 6 所示的測試用例,它也包含一個參數,用于進行測試的應用程序的基 URL。該測試將促使瀏覽器在 Web 應用程序內打開特定頁面,并操作 圖 1 所示的表單。
@Parameters({"aut-addr"}) @Test public void verifyCreate(String appPath) throws Exception { driver.open(appPath + "/CreateWidget.html"); driver.type("widget", "book-01"); driver.select("type", "book"); driver.type("definition", "book widget type book"); driver.click("submit"); driver.waitForPageToLoad("10000"); assertEquals(driver.getText("success"), "The widget book-01 was successfully created.", "test didn't return expected message"); } |
通過調用 driver.click("submit")
提交表單后,Selenium 將等待響應的加載,然后我將斷言成功的創建信息。(注意:響應 Web 頁面具有一個 ID 為 success 的元素。)
結果產生一個靈活的文本類,它將檢驗兩種場景:一種是良好的場景,而另一種是沒有提供定義的邊界用例,如清單 7 所示:
public class CreateWidgetUATest { private Selenium driver; @Parameters({"selen-svr-addr","brwsr-path","aut-addr"}) @BeforeClass private void init(String selenSrvrAddr, String bpath, String appPath) throws Exception { driver = new DefaultSelenium(selenSrvrAddr, SeleniumServer.getDefaultPort(), bpath, appPath); driver.start(); } @Parameters({"aut-addr"}) @Test public void verifyCreate(String appPath) throws Exception { driver.open(appPath + "/CreateWidget.html"); driver.type("widget", "book-01"); driver.select("type", "book"); driver.type("definition", "book widget type book"); driver.click("submit"); driver.waitForPageToLoad("10000"); assertEquals(driver.getText("success"), "The widget book-01 was successfully created.", "test didn't return expected message"); } @Parameters({"aut-addr"}) @Test public void verifyCreationError(String appPath) throws Exception { driver.open(appPath + "/CreateWidget.html"); driver.type("widget", "book-02"); driver.select("type", "book"); //definition explicitly set to blank driver.type("definition", ""); driver.click("submit"); driver.waitForPageToLoad("10000"); assertEquals(driver.getText("failure"), "There was an error in creating the widget.", "test didn't return expected message"); } @AfterClass private void stop() throws Exception { driver.stop(); }} |
目前為止,我已經定義了兩種足夠靈活的 Selenium 測試,可以對多個瀏覽器進行測試,并且還可以對多個位置進行測試,這對初學者非常有利。盡管如此,我還想獲得更高級點的應用,我開始考慮測試中的邏輯是否可重復使用。比如,如果對一行運行兩次 CreateWidgetUATest
測試類會怎樣?如何確保我的 Web 應用程序運行的是本地機器(或其他機器)上最新版本的代碼?
![]() ![]() |
![]()
|
在執行 Selenium 測試時,必須運行 Selenium 服務器以及要檢驗的 Web 應用程序。言外之意,還必須運行應用程序中所有相關的架構依賴關系 —— 對于大多數 Java™ Web 應用程序來說,即 Servlet 容器和相關的數據庫。
正如在我的另一篇文章 repeatable system tests 中解釋的一樣,DbUnit 和 Cargo 是兩種我最喜歡的技術,可以在依賴數據庫的 Web 應用程序中實現邏輯重復。DbUnit 管理數據庫中的數據,而 Cargo 使容器管理以通用的方式實現自動化。下面幾節將向您展示如何結合使用 Selenium 和 TestNG 從而確保實現邏輯重復的驗收測試。
![]() ![]() |
![]()
|
您可能回想起,DbUnit 通過有效地管理測試場景中的數據簡化了使用數據庫的工作。通過使用 DbUnit,可以在測試前將一組已知的數據加載到數據庫中,這意味著您可以依賴這些在測試過程中呈現的數據。此外,在完成測試后,還可以從數據庫中刪除測試結果產生的數據。DbUnit 作為一種方便的 fixture(JUnit 或 TestNG)簡化了所有這些工作,它能夠讀取包含測試數據的種子文件,邏輯插入、刪除數據,或更新數據到相應的數據庫表中。
由于這里使用了 TestNG 驅動 Selenium,我將創建一個 DbUnit fixture,它將在測試 級別上運行。TestNG 支持在五種粒度級別上運行 fixture。最低的兩種級別,方法和類是最常見的 —— 用于每個測試方法的 fixture 或者用于整個類的 fixture。之后,TestNG 為一個測試集合(定義在 TestNG 配置文件中并由 test
元素指定)定義了一個 fixture,為一組 測試(定義在 TestNG 的 Test
注釋中)定義了一個 fixture。
創建一個 DbUnit fixture 并在測試級別上運行,這意味著運行任何測試之前,測試類的集合將共享相同的邏輯,為數據庫正確地播種。在本文的示例中,在運行每個邏輯測試集合前,我希望數據庫具有一組干凈的數據。使用 DbUnit 的 CLEAN_INSERT
命令確保在先前運行的測試中創建的行被刪除掉 —— 因此,我可以重新運行測試,該測試可以不斷創建數據并且不用考慮數據庫約束。
此外,我希望 fixture 能夠依賴參數化數據,這使我在運行某個測試之前,能夠靈活地切換種子文件,甚至是特定數據庫的位置。將 TestNG 與參數相關聯起來再簡單不過了:我所需做的僅僅是使用 Parameters
注釋裝飾 fixtrue,聲明方法簽名中相應的參數,并提供 TestNG 配置文件中的值。
清單 8 定義了一個簡單的 DbUnit fixture,它使用所需的種子文件播種數據庫。請注意:該 fixture 被定義為包含五個 參數。(這可能非常多,但是在 fixture 中包含參數不是很好嗎?)
public class DatabaseFixture { @Parameters({"seed-path","db-driver","db-url","db-user","db-psswrd"}) @BeforeTest public void seedDatabase(String seedpath, String driver, String url, String user, String pssword) throws Exception { IDatabaseConnection conn = this.getConnection(driver, url, user, pssword); IDataSet data = this.getDataSet(seedpath); try { DatabaseOperation.CLEAN_INSERT.execute(conn, data); }finally { conn.close(); } } private IDataSet getDataSet(String path) throws IOException, DataSetException { return new FlatXmlDataSet(new File(path)); } private IDatabaseConnection getConnection(String driver, String url, String user, String pssword ) throws ClassNotFoundException, SQLException { Class.forName(driver); Connection jdbcConnection = DriverManager.getConnection(url, user, pssword); return new DatabaseConnection(jdbcConnection); }} |
要將實際的值與清單 8 中的參數相關聯,我必須在 TestNG 的 testng.xml
文件中定義它們,如清單 9 所示:
<parameter name="seed-path" value="test/conf/gt15-seed.xml"/> <parameter name="db-driver" value="org.hsqldb.jdbcDriver"/> <parameter name="db-url" value="jdbc:hsqldb:hsql://127.0.0.1"/> <parameter name="db-user" value="sa"/> <parameter name="db-psswrd" value=""/> |
現在我已經定義了一個靈活的 fixture,它將處理數據庫狀態和相應測試?,F在可以準備使用 TestNG 將所有內容連接起來。通常,第一步是了解希望實現的內容。在本例中,我想完成以下任務:
TestNG 的 parameter
元素的作用域是局部的,這對我來說是件好事。這樣,我可以很容易地在 TestNG 配置文件中定義通用參數值,并且當需要時在 TestNG 的 test
組元素中重寫它們。
比如,要運行兩組測試,簡單創建兩個 test
元素。我可以通過 TestNG 的 package
元素將我的 fixture 和相關測試包括進來,package
元素能夠使包結構中所有測試(或 fixture)的查找變得簡單。接著,我可以在兩個定義了的 test
組中將 Firefox 和 Internet Explorer 的 brwsr-path
參數關聯起來。所有這些都顯示在了 testng.xml 文件中,如清單 10 所示:
<suite name="User Acceptance Tests" verbose="1" > <!-- required for DbUnit fixture --> <parameter name="seed-path" value="test/conf/gt15-seed.xml"/> <parameter name="db-driver" value="org.hsqldb.jdbcDriver"/> <parameter name="db-url" value="jdbc:hsqldb:hsql://127.0.0.1"/> <parameter name="db-user" value="sa"/> <parameter name="db-psswrd" value=""/> <!-- required for Selenium fixture --> <parameter name="selen-svr-addr" value="localhost"/> <parameter name="aut-addr" value="http://localhost:8080/gt15/"/> <test name="GT15 CRUDs- Firefox" > <parameter name="brwsr-path" value="*firefox"/> <packages> <package name="test.com.acme.gt15.Web.selenium" /> <package name="test.com.acme.gt15.Web.selenium.fixtures" /> </packages> </test> <test name="GT15 CRUDs- IE" > <parameter name="brwsr-path" value="*iexplore"/> <packages> <package name="test.com.acme.gt15.Web.selenium" /> <package name="test.com.acme.gt15.Web.selenium.fixtures" /> </packages> </test></suite> |
我很高興地宣布,我已經完成了創建一套可重復驗收測試所需的所有事情。剩下的工具就是處理 Web 應用程序容器本身。幸運地是,我可以使用 Cargo 來完成。
![]() ![]() |
![]()
|
Cargo 是一個創新的以通用方式自動化容器管理的開源項目,比如,用于將 WAR 文件部署到 JBoss 的相同 API 還可以啟動和停止 Tomcat。Cargo 還可以自動下載并安裝容器 —— Cargo API 的用途很廣泛,從 Java 代碼到 Ant 任務,甚至是 Maven。
諸如 Cargo 這樣的工具將處理編寫邏輯重復測試用例所面對的一個大的挑戰,它避免一種潛在的假設,即運行 的容器具有最新最好的應用程序代碼。此外,還可以構造一個利用 Cargo 的能力自動完成以下任務的編譯過程(例如在 Ant 內):
稍后,您還可以使 Cargo 停止所選的容器。(并且,不需要對下載和安裝容器發出警告,或者,如果本地機器中已經存在了正確的版本,Cargo 將跳過步驟 1 和 2。)
我希望使用 Cargo 來確保啟動并運行最新和最好的 Web 應用程序。并且,我不需要考慮在哪里部署 WAR 文件,或者必須確保正在使用的是最新的 WAR 文件。我真正想達到的目的是使用戶驗收測試實現無事件 —— 我僅需要發出一個 命令,然后坐下來等待結果。甚至可以更好,在一個 CI 環境中,我不用等待;當測試完成后我將獲得一個通知!
要在 Ant 內設置 Cargo,我需要定義一個任務,它將下載特定版本的 Tomcat 并將其安裝到本地機器上的臨時目錄。接下來,將最新版本的代碼部署到 Tomcat 上,如清單 11 所示:
<target name="ua-test" depends="compile-tests,war"> <taskdef resource="cargo.tasks"> <classpath> <pathelement location="${libdir}/${cargo-jar}" /> <pathelement location="${libdir}/${cargo-ant-jar}" /> </classpath> </taskdef> <cargo containerId="tomcat5x" action="start" wait="false" id="${tomcat-refid}"> <zipurlinstaller installurl="${tomcat-installer-url}" /> <configuration type="standalone" home="${tomcatdir}"> <property name="cargo.remote.username" value="admin" /> <property name="cargo.remote.password" value="" /> <deployable type="war" file="${wardir}/${warfile}" /> </configuration> </cargo> <antcall target="_start-selenium" /> <cargo containerId="tomcat5x" action="stop" refid="${tomcat-refid}" /></target> |
清單 11 中的 target 使用 antcall
調用另一個 target。實際上,清單 11 中最后的 cargo
任務封裝了 _start-selenium
target,并且確保運行測試后停止 Tomcat。
在清單 12 中定義的 _start-selenium
target 中,我需要啟動(并稍后停止)Selenium 服務器。在此過程中,我的測試還將連接到其 Selenium fixture 中的服務器實例。請注意:該 target 是如何引用另一個 target ——
<target name="_start-selenium"> <java jar="${libdir}/${selenium-srvr-jar}" fork="true" spawn="true" /> <antcall target="_run-ua-tests" /> <get dest="${testreportdir}/results.txt" src="${selenium-srvr-loc}/selenium-server/driver/?cmd=shutDown" /></target> |
最后,該組中最后的 target 將通過 TestNG 實際運行我的編程式 Selenium 測試。注意,我是如何通過使用清單 13 中的 _run-ua-tests
target 的 xmlfileset
元素,使 TestNG 使用我的 testng.xml 文件。
<target name="_run-ua-tests"> <taskdef classpathref="build.classpath" resource="testngtasks" /> <testng outputDir="${testreportdir}" classpath="${testclassesdir};${classesdir}" haltonfailure="true"> <xmlfileset dir="./test/conf" includes="testng.xml" /> <classpath> <path refid="build.classpath" /> </classpath> </testng></target> |
![]() ![]() |
![]()
|
正如您看到的一樣,Selenium 極大地簡化了用戶驗收測試,尤其當使用 TestNG 驅動的時候。雖然編程式測試并不適用于所有人(非開發人員可能更喜歡 Selenium 的 Fit 樣式的表),它確實讓您了解到了 TestNG 非凡的靈活性。編程式測試還允許您使用 DbUnit 和 Cargo 構建自己的測試框架,從而確保測試的邏輯可重復性。
開源 Web 測試框架的發展絕不會停止,這對于追求代碼質量的完美主義者是個好消息。Selenium 是驅動瀏覽器的開源 Web 測試框架中新出現的工具之一,它能夠使用戶驗收測試自動化 —— 因此,它非常優秀。結合使用 Selenium 和 TestNG,正如我在本文中演示的一樣,您將獲得一個非常好的測試驅動,并從依賴性測試以及參數測試中獲得巨大的優勢。嘗試使用 Selenium 和 TestNG 吧,您的用戶將為此感謝您。
原文轉自:http://www.anti-gravitydesign.com