我已經使用了一段時間,遇到了一些似乎可以使生活更輕松的事情。 我以為可以將其作為教程分享,所以我將向您介紹這些部分:
- 使用Maven設置Web項目,配置Selenium以在CI上作為集成測試運行
- 尋找使用“頁面對象”為網站中的頁面建模的好方法,以及其他創建受保護的變量的方法。
- 使用JPA和Hibernate對數據庫執行CRUD操作,并讓Maven對它們執行集成測試,而無需進行有時會涉及的任何昂貴且通常沒有文檔的設置。
這篇文章假定您對Java,Spring,Maven 2和HTML感到滿意。 您還需要在計算機上安裝Firefox。 本教程旨在以其他方式與技術無關。
創建一個Webapp
首先,我們需要一個webapp進行測試。 使用maven-webapp-archetype創建一個項目,并將其稱為“ selenuim-tutorial”。
要運行集成測試(IT),我們將使用Cargo插件。 這將啟動和停止Jetty和Tomcat之類的容器。 您可以使用Cargo在一個命令中使用Jetty(默認設置)啟動網站,而無需進行任何更改:
mvn cargo:run
并在瀏覽器中檢查它:
http:// localhost:8080 / selenuim-tutorial
您將獲得一個沒有歡迎文件設置的404,因此將其添加到web.xml文件中:
<welcome-file-list><welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
如果您運行貨物:再次運行,您現在將看到“ Hello World!” 由Maven創建的頁面。
配置貨物
我們可以將Cargo設置為在運行測試之前啟動Jetty容器,然后再將其停止。 這將使我們能夠啟動站點,運行集成測試,然后再將其停止。
<plugin><groupId>org.codehaus.cargo</groupId><artifactId>cargo-maven2-plugin</artifactId><version>1.2.0</version><executions><execution><id>start</id><phase>pre-integration-test</phase><goals><goal>start</goal></goals></execution><execution><id>stop</id><phase>post-integration-test</phase><goals><goal>stop</goal></goals></execution></executions>
</plugin>
您可以使用以下方法測試這項工作:
mvn verify
此時要注意的一件事是Cargo運行在端口8080上。如果您已經有一個進程在該端口上進行偵聽,則可能會看到類似以下錯誤:
java.net.BindException: Address already in use
這可能是因為您已經在該端口上運行了另一個容器。 如果要在CI上運行它(它本身可以在端口8080上運行),則可能需要更改。 將這些行添加到插件設置中:
<configuration><type>standalone</type><configuration><properties><cargo.servlet.port>10001</cargo.servlet.port></properties></configuration>
</configuration>
現在該應用程序將在這里:
http:// localhost:10001 / selenuim-tutorial /
設置集成測試階段
接下來,我們需要能夠運行集成測試。 這需要Maven故障安全插件,并將適當的目標添加到pom中:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-failsafe-plugin</artifactId><version>2.12</version><executions><execution><id>default</id><goals><goal>integration-test</goal><goal>verify</goal></goals></execution></executions>
</plugin>
默認情況下,Failsafe期望測試匹配模式“ src / test / java / * / * IT.java”。 讓我們創建一個測試來證明這一點。 請注意,我尚未從Junit 3.8.1更改過。 我稍后會解釋原因。
這是一個基本的,不完整的測試:
package tutorial;import junit.framework.TestCase;public class IndexPageIT extends TestCase {@Overrideprotected void setUp() throws Exception {super.setUp();}@Overrideprotected void tearDown() throws Exception {super.tearDown();}public void testWeSeeHelloWorld() {fail();}
}
測試有效:
mvn verify
您應該看到一個測試失敗。
要使用Selenium進行測試,您需要向pom.xml添加測試范圍的依賴項:
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-firefox-driver</artifactId><version>2.19.0</version><scope>test</scope>
</dependency>
現在,我們可以對測試進行一些更改:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;…private URI siteBase;private WebDriver drv;@Overrideprotected void setUp() throws Exception {super.setUp();siteBase = new URI("http://localhost:10001/selenuim-tutorial/");drv = new FirefoxDriver();}...public void testWeSeeHelloWorld() {drv.get(siteBase.toString());assertTrue(drv.getPageSource().contains("Hello World"));}
稍后我們將刪除這些硬編碼值。
再次運行:
mvn verify
您應該不會看到任何故障。 您將擁有一個揮之不去的Firefox。 它不會關閉。 運行此測試100次,您將運行100個Firefox。 這將很快成為一個問題。 我們可以通過在測試中添加以下初始化塊來解決此問題:
{Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic void run() {drv.close();}});}
自然,如果我們創建另一個測試,我們將很快違反DRY原則。 我們將在下一部分中討論該問題,并查看需要數據庫連接時發生的情況,以及其他一些方法來確保您的測試易于編寫和維護。
Spring語境
在前面的示例中,應用程序的URI和使用的驅動程序都經過了硬編碼。 假設您熟悉Spring上下文,那么更改這些上下文就很簡單了。 首先,我們將添加正確的依賴項:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>3.1.1.RELEASE</version><scope>test</scope>
</dependency>
這將使我們能夠使用和應用程序上下文注入依賴項。 但是我們還需要正確的Junit運行程序來測試它,可以在spring-test軟件包中找到它:
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>3.1.1.RELEASE</version><scope>test</scope>
</dependency>
現在,我們可以更新測試以使用它。 首先,我們需要創建src / test / resources / applicationContext-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><bean id="siteBase" class="java.net.URI"><constructor-arg value="http://localhost:10001/selenuim-tutorial/" /></bean><bean id="drv" class="org.openqa.selenium.firefox.FirefoxDriver" destroy-method="quit"/>
</beans>
Spring完成后將清除瀏覽器,因此我們可以從AbstractIT中刪除關閉鉤子。 這比讓測試用例執行此操作更健壯。
彈簧測試不適用于JUnit 3,它至少需要JUnit 4.5。 讓我們在pom.xml中更新到版本4.10:
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope>
</dependency>
最后,我們需要更新測試以同時使用Spring和JUnit 4.x:
package tutorial;import static org.junit.Assert.assertTrue;import java.net.URI;import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext-test.xml" })
public class IndexPageIT {@Autowiredprivate URI siteBase;@Autowiredprivate WebDriver drv;@Testpublic void testWeSeeHelloWorld() {
...
這些更改將配置從硬編碼值轉移到XML配置。 現在,我們可以將要測試的位置更改為例如其他主機,并更改我們正在使用的Web驅動程序,這留給用戶練習。
關于瀏覽器的快速說明。 我發現瀏覽器更新后,測試通常會開始失敗。 似乎有兩種解決方案:
- 升級到最新版本的Web驅動程序。
- 不要升級瀏覽器。
我出于安全原因懷疑,在大多數情況下,第一種選擇是最好的
抽象IT
當前,您需要復制IoC的所有代碼。 一個簡單的重構可以解決這個問題。 我們將為所有測試和上拉通用功能創建一個超類。 出于重構的目的,繼承使用的是繼承,而不是合成。
package tutorial;import java.net.URI;import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext-test.xml" })
public abstract class AbstractIT {@Autowiredprivate URI siteBase;@Autowiredprivate WebDriver drv;public URI getSiteBase() {return siteBase;}public WebDriver getDrv() {return drv;}
}
package tutorial;import static org.junit.Assert.assertTrue;import org.junit.Test;public class IndexPageIT extends AbstractIT {@Testpublic void testWeSeeHelloWorld() {getDrv().get(getSiteBase().toString());assertTrue(getDrv().getPageSource().contains("Hello World"));}
}
頁面對象
“頁面對象”是封裝頁面的單個實例并為該實例提供程序化API的對象。 基本頁面可能是:
package tutorial;import java.net.URI;import org.openqa.selenium.WebDriver;public class IndexPage {/*** @param drv* A web driver.* @param siteBase* The root URI of a the expected site.* @return Whether or not the driver is at the index page of the site.*/public static boolean isAtIndexPage(WebDriver drv, URI siteBase) {return drv.getCurrentUrl().equals(siteBase);}private final WebDriver drv;private final URI siteBase;public IndexPage(WebDriver drv, URI siteBase) {if (!isAtIndexPage(drv, siteBase)) { throw new IllegalStateException(); }this.drv = drv;this.siteBase = siteBase;}
}
請注意,我已經提供了一個靜態方法來返回我們是否在索引頁上,并且已經對其進行了注釋(對于這種自填充方法,這是不必要的)。 頁面對象形成一個API,值得記錄。 您還將看到,如果URL不正確,我們將引發異常。 值得考慮使用什么條件來識別頁面。 任何可能更改的內容(例如頁面標題,可能會在不同語言之間更改)都是一個糟糕的選擇。 不變的東西和機器可讀的東西(例如頁面的路徑)是不錯的選擇。 如果要更改路徑,則需要更改測試。
現在讓我們自己制造一個問題。 我想將其添加到index.jsp,但是生成HTML無法解析:
<% throw new RuntimeException(); %>
相反,我們將創建一個新的servlet,但是首先我們需要將servlet-api添加到pom.xml中:
<dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope>
</dependency>
package tutorial;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class IndexServlet extends HttpServlet {private static final long serialVersionUID = 1L;protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {throw new RuntimeException();}
}
將其添加到web.xml并刪除現在不必要的歡迎頁面:
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app><servlet><servlet-name>IndexServlet</servlet-name><servlet-class>tutorial.IndexServlet</servlet-class></servlet><servlet-mapping><servlet-name>IndexServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
更新IndexPageIT:
@Testpublic void testWeSeeHelloWorld() {getDrv().get(getSiteBase().toString());new IndexPage(getDrv(), getSiteBase());}
再次運行測試。 它通過了。 這可能不是您想要的行為。 Selenium沒有提供通過WebDriver實例檢查HTTP狀態代碼的方法。 容器之間的默認錯誤頁面也沒有足夠一致(例如,與在Tomcat上運行時所發生的情況進行比較); 我們無法對錯誤頁面的內容進行假設以判斷是否發生錯誤。
我們的索引頁面當前不具備任何機器可讀功能,因此無法從錯誤頁面中分辨出來。
要整理,請修改servlet以顯示index.jsp:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {getServletContext().getRequestDispatcher("/index.jsp").forward(request, response);}
當前index.jsp有點太簡單了。 在index.jsp旁邊創建一個名為create-order.jsp的新頁面,并在index.jsp上創建指向該頁面的鏈接。 我們可以為訂單頁面創建一個新類,并創建一個將我們從索引頁面導航到訂單頁面的方法。
將以下內容添加到index.jsp中:
<a href="create-order.jsp">Create an order</a>
create-order.jsp現在可以為空。 我們還可以為其創建一個頁面對象:
package tutorial;import java.net.URI;import org.openqa.selenium.WebDriver;public class CreateOrderPage {public static boolean isAtCreateOrderPage(WebDriver drv, URI siteBase) {return drv.getCurrentUrl().equals(siteBase.toString() + "create-order.jsp");}private final WebDriver drv;private final URI siteBase;public CreateOrderPage(WebDriver drv, URI siteBase) {if (!isAtCreateOrderPage(drv, siteBase)) { throw new IllegalStateException(); }this.drv = drv;this.siteBase = siteBase;}
}
將以下依賴項添加到pom.xml中,這將為我們提供一些有用的注釋:
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-support</artifactId><version>2.19.0</version><scope>test</scope>
</dependency>
我們現在可以充實IndexPage了:
@FindBy(css = "a[href='create-order.jsp']")private WebElement createOrderLink;public IndexPage(WebDriver drv, URI siteBase) {if (!isAtIndexPage(drv, siteBase)) { throw new IllegalStateException(); }PageFactory.initElements(drv, this);this.drv = drv;this.siteBase = siteBase;}
對PageFactory.initElements的此調用將填充帶有@FindBy注釋的字段,該對象具有與網頁上的元素匹配的對象。 請注意,使用CSS選擇器是為了以不太可能更改的方式定位鏈接。 其他方法包括使用鏈接文本匹配頁面上的元素(可能會因不同的語言而改變)。
現在,我們可以在IndexPages上創建一個方法,該方法導航到CreateOrderPages。
public CreateOrderPage createOrder() {createOrderLink.click();return new CreateOrderPage(drv, siteBase);}
最后,我們可以在IndexPageIT中為此鏈接創建一個測試:
@Testpublic void testCreateOrder() {getDrv().get(getSiteBase().toString());new IndexPage(getDrv(), getSiteBase()).createOrder();assertTrue(CreateOrderPage.isAtCreateOrderPage(getDrv(), getSiteBase()));}
執行mvn verify,您應該找到新的測試通過。 至此,我們有兩個測試無法在它們之間進行清理。 他們在兩個測試中使用相同的WebDriver實例,最后一頁仍將打開,并且設置的所有cookie都將保持不變。 為多個測試創建單個WebDriver實例的優缺點。 主要優點是減少了打開和關閉瀏覽器的時間,但缺點是每次測試,Cookie設置和彈出窗口打開后,瀏覽器實際上都會變臟。 我們可以使用AbstractIT中的適當setUp方法確保每次測試之前它都是干凈的:
@Beforepublic void setUp() {getDrv().manage().deleteAllCookies();getDrv().get(siteBase.toString());}
有其他方法可以解決,我將讓您自行研究在每次測試之前創建新的WebDriver實例的方法。
@FindBy批注在窗體上使用時特別有用。 向create-order.jsp添加新表單:
<form method="post" name="create-order">Item: <input name="item"/> <br/>Amount: <input name="amount"/><br/><input type="submit"/></form>
將這些WebElement添加到CreateOrderPage中,并添加一種提交表單的方法:
@FindBy(css = "form[name='create-order'] input[name='item']")private WebElement itemInput;@FindBy(css = "form[name='create-order'] input[name='amount']")private WebElement amountInput;@FindBy(css = "form[name='create-order'] input[type='submit']")private WebElement submit;public CreateOrderPage(WebDriver drv, URI siteBase) {if (!isAtCreateOrderPage(drv, siteBase)) { throw new IllegalStateException(); }PageFactory.initElements(drv, this);this.drv = drv;this.siteBase = siteBase;}public CreateOrderPage submit(String item, String amount) {itemInput.sendKeys(item);amountInput.sendKeys(amount);submit.click();return new CreateOrderPage(drv, siteBase);}
最后,我們可以為此創建一個測試:
package tutorial;import static org.junit.Assert.*;import org.junit.Test;public class CreateOrderPageIT extends AbstractIT {@Testpublic void testSubmit() {new IndexPage(getDrv(), getSiteBase()).createOrder().submit("foo", "1.0");}
}
結論
您可能要注意的一件事是,submit方法不需要將金額作為您期望的數字。 您可以創建一個測試以查看提交的是字符串而不是數字。 集成測試的編寫可能很耗時,并且由于諸如元素ID或輸入名稱之類的事物的更改而容易損壞。 結果,創建它們所獲得的最大好處是,最初僅在您站點內的業務關鍵路徑上創建它們,例如,產品訂購,客戶注冊流程和付款。
在本教程的下一部分中,我們將研究使用一些數據支持測試以及由此帶來的挑戰。
參考:我們的JCG合作伙伴 Alex Collins在Alex Collins的博客上提供的教程 : 教程:與硒的集成測試–第1部分 , 教程:與硒的集成測試–第2部分 。
翻譯自: https://www.javacodegeeks.com/2012/04/integration-testing-with-selenium.html