程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 追求代碼質量 - 使用Selenium和TestNG進行編程式測試

追求代碼質量 - 使用Selenium和TestNG進行編程式測試

編輯:關於JAVA

Selenium 是一種 Web 測試框架,它搭建了驗證 Web 應用程序的新途徑。與 大多數嘗試模擬 HTTP 請求的 Web 測試工具不同,Selenium 執行 Web 測試時 ,就仿佛它本身就是浏覽器。當運行自動的 Selenium 測試時,該框架將啟動一 個浏覽器,並通過測試中描述的步驟實際驅動浏覽器,用戶將使用這種方式與應 用程序交互。

由於開發人員和非開發人員都能夠使用 Selenium 輕松地編寫測試,使得它 從眾多測試框架應用程序中脫穎而出。在 Selenium 中,可以通過編程的方式編 寫測試,或者使用 Fit 樣式的表,並且編寫了測試後,可以使測試完全自動化 。使用一個 Ant 構件(比方說)運行完整的 Selenium 套件非常簡單,並且還 可以在持續集成(Continuous Integration,CI)環境中運行 Selenium 測試。

這個月,我將介紹 Selenium,並逐一查看使它成為優秀 Web 測試框架的一 些特性 —— 尤其是在結合使用 TestNG、DbUnit 和 Cargo 這樣的軟件時。

驗收測試

由於 Selenium 能夠很好地模擬用戶的行為,它常常用於進行驗 收測試,即在完成的系統上運行一整套測試。驗收測試通常需要運行整個應用程 序,以使測試發揮作用。如果您要測試一個 Web 應用程序,則需要訪問應用程 序數據庫,以及一台 Web 服務器,一個容器和運行應用程序所需的任何配置元 素。

使用 Selenium 進行編程式測試

在 Selenium 中,您可以使用自己喜愛的語言或者 Fit 樣式的表通過編程來 編寫測試。從測試的角度來說,不管使用什麼語言,測試過程和結果都不會有顯 著的差別。在此,我希望研究 Selenium 的編程方法,因為在結合使用 TestNG 時,它提供了一些有趣的可行方法能性。

使用具有類似 TestNG 這樣的框架的 Selenium 進行編程式測試具有這樣一 個優點,它允許您創建智能 fixture,而使用 Fit 樣式的表則很難做到這一點 。TestNG 尤其適合與 Selenium 結合使用,因為它使您能夠完成其他框架無法 做到的測試,例如使用依賴項進行測試,重新運行失敗了的測試,以及使用單獨 文件中定義的參數進行參數化測試。所有這些特性結合在一起,當然能夠使它在 眾多 Web 應用程序測試框架中脫穎而出,但是,正如您將看到的,在完全自動 化的驗收測試中使用這些特性令它更加出眾。

配置第一個測試

Selenium 架構實際上由兩個邏輯實體組成:您編寫的代碼以及能夠簡化與測 試中的應用程序的交互的 Selenium 服務器。要成功地執行測試,必須要啟動並 運行 Selenium 服務器實例以及要測試的應用程序。(當然,測試結果取決於您 編寫的應用程序是否優秀!)

幸運的是,Selenium 服務器是一種輕量級程序,可以在實際的測試范圍內通 過編程啟動和停止它。Selenium 服務器(使用 Selenium 對象嵌入)的啟動和 停止由一個 fixture 來執行。

要通過編程的方式啟動 Selenium 服務器,必須創建一個新的 Selenium 對 象,並告訴它要使用哪一種兼容的浏覽器 —— 我在下面的示例中使用的是 Firefox。您還必須提供運行服務器實例的位置(通常是 localhost,但不是必 須的),以及被測試的應用程序使用的基 URL。

在清單 1 中,我配置了一個本地 Selenium 實例,使用它在本地安裝的 Web 應用程序上驅動 Firefox(http://localhost:8080/gt15/)。正如您從參數中 推斷的一樣,Selenium 是作為被測試的應用程序的代理,並相應地促進測試。

清單 1. 配置 SeleniumServer

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 的應用。與頁面元素進行 交互的第一步就是查找該元素,通常可以使用 HTML 元素 ID 進行查找。 Selenium 還允許您使用 XPath、正則表達式,甚至是 JavaScript 來查找特定 的元素(如果您希望這樣做)。

清單 2 所示的 HTML 是使用 Groovlet 的簡單 Web 應用程序的一部分。這 段代碼定義了包含輸入和提交按鈕的表單。如果希望 Selenium 與該表單交互, 我必須為輸入按鈕提供 ID 以及相應的值。我還需要為提交按鈕提供一個 ID, 這樣 Selenium 才能 “單擊” 它。單擊按鈕後,表單將被提交給 Groovlet — — 本例中為 FindWidget.groovy。

清單 2. 簡單的 HTML 表單

<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 所示:

清單 3. 驅動簡單的 Web 頁面

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 中的代碼運行其內容並返回 響應後,我可以使用該響應來查找特定頁面元素,並驗證所有內容是否正常工作 。

Selenium 和 TestNG

TestNG 以其靈活性和參數化 fixture 成為定義 Selenium 的驅動驗收測試 的首選。TestNG 能夠定義測試依賴項並返回失敗的測試,以及其易用性,使得 Selenium-TestNG 成為吸引人的組合。

讓我們首先從一個能夠允許用戶創建、查找、更新或刪除小部件的 Web 應用 程序開始。創建一個小部件需要三個屬性:名稱、類型和定義。圖 1 顯示了創 建小部件的表單:

圖 1. 創建小部件的 Web 表單

請注意:表單元素的類型是具有三個不同選項的下拉列表,如圖 2 所示:

圖 2. 包含下拉列表的 Web 表單

單擊 Create Widget 將促使 Groovlet 處理這一請求。如果所有內容正確的 話(即名字和定義不為空,並且數據庫中不存在該實例),Groovlet 將創建一 個新的小部件實例並類似圖 3 所示的狀態頁面:

圖 3. 返回的 Web 頁面顯示狀態

結合使用 Selenium 和 TestNG 驗證簡單的 Create Widget 用例是一種可管 理的應用:

配置並啟動 Selenium 服務器的實例。

與 Create Widget Web 表單交互並提交它。

檢驗結果頁面是否包含具有小部件名稱的成功信息。

停止 Selenium 服務器實例。

請注意:用例中的每一步都是通過 Selenium 完成的 —— 所以說,TestNG 僅僅幫助進行查找。現在,我們來實踐一下。

Create Widget 測試用例

我希望對 Selenium 服務器進行靈活的配置,所以我將編寫一個參數化 fixture(TestNG-Selenium 樣式),一般可以使用它來為不同浏覽器、不同位 置甚至混合的 Web 應用程序地址(類似 localhost 和產品)創建 Selenium 服 務器。清單 4 定義了我所配置的靈活的 Selenium 服務器 fixture:

清單 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 的測試。 )

清單 5. TestNG testng.xml 文件中的參數值

<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 所示的表單。

清單 6. 一個良好的測試用例

@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 所示:

清單 7. 使用 TestNG 進行全部的處理

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,可以在測試前將一組已知的數據加載 到數據庫中,這意味著您可以依賴這些在測試過程中呈現的數據。此外,在完成 測試後,還可以從數據庫中刪除測試結果產生的數據。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 中包含參數不是很好嗎?)

清單 8. 測試集合的 DbUnit 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 所示:

清單 9. TestNG 的 testng.xml 文件中定義的特定於 DbUnit 的參數

<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,它將處理數據庫狀態和相應測試。 現在可以准備使用 TestNG 將所有內容連接起來。通常,第一步是了解希望實現 的內容。在本例中,我想完成以下任務:

我希望在運行任何邏輯測試集合前,DbUnit fixture 能夠完成自己任務。

我希望將相同的測試集合運行兩次:一次用於 Firefox,一次用於 Internet Explorer。

TestNG 的 parameter 元素的作用域是局部的,這對我來說是件好事。這樣 ,我可以很容易地在 TestNG 配置文件中定義通用參數值,並且當需要時在 TestNG 的 test 組元素中重寫它們。

比如,要運行兩組測試,簡單創建兩個 test 元素。我可以通過 TestNG 的 package 元素將我的 fixture 和相關測試包括進來,package 元素能夠使包結 構中所有測試(或 fixture)的查找變得簡單。接著,我可以在兩個定義了的 test 組中將 Firefox 和 Internet Explorer 的 brwsr-path 參數關聯起來。 所有這些都顯示在了 testng.xml 文件中,如清單 10 所示:

清單 10. 使 DbUnit 運行的靈活的 testng.xml 文件

<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 執行加載

Cargo 是一個創新的以通用方式自動化容器管理的開源項目,比如,用於將 WAR 文件部署到 JBoss 的相同 API 還可以啟動和停止 Tomcat。Cargo 還可以 自動下載並安裝容器 —— Cargo API 的用途很廣泛,從 Java 代碼到 Ant 任 務,甚至是 Maven。

諸如 Cargo 這樣的工具將處理編寫邏輯重復測試用例所面對的一個大的挑戰 ,它避免一種潛在的假設,即運行 的容器具有最新最好的應用程序代碼。此外 ,還可以構造一個利用 Cargo 的能力自動完成以下任務的編譯過程(例如在 Ant 內):

下載所需的容器。

安裝該容器。

啟動容器。

將選擇的 WAR 或 EAR 文件部署到容器上。

稍後,您還可以使 Cargo 停止所選的容器。(並且,不需要對下載和安裝容 器發出警告,或者,如果本地機器中已經存在了正確的版本,Cargo 將跳過步驟 1 和 2。)

我希望使用 Cargo 來確保啟動並運行最新和最好的 Web 應用程序。並且, 我不需要考慮在哪裡部署 WAR 文件,或者必須確保正在使用的是最新的 WAR 文 件。我真正想達到的目的是使用戶驗收測試實現無事件 —— 我僅需要發出一個 命令,然後坐下來等待結果。甚至可以更好,在一個 CI 環境中,我不用等待; 當測試完成後我將獲得一個通知!

測試容器管理

要在 Ant 內設置 Cargo,我需要定義一個任務,它將下載特定版本的 Tomcat 並將其安裝到本地機器上的臨時目錄。接下來,將最新版本的代碼部署 到 Tomcat 上,如清單 11 所示:

清單 11. 設置 Cargo 的任務

<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 ——

清單 12. 啟動和停止 Selenium 服務器

<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 文件。

清單 13. 運行 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 吧, 您的用戶將為此感謝您。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved