与Selenium的集成测试
我已經(jīng)使用了一段時(shí)間,并且遇到了一些似乎可以使生活更輕松的事情。 我以為可以將其作為教程分享,所以我將向您介紹這些部分:
這篇文章假定您對(duì)Java,Spring,Maven 2以及HTML感到滿意。 您還需要在計(jì)算機(jī)上安裝Firefox。 本教程旨在以其他方式與技術(shù)無關(guān)。
創(chuàng)建一個(gè)Webapp
首先,我們需要一個(gè)webapp進(jìn)行測試。 使用maven-webapp-archetype創(chuàng)建一個(gè)項(xiàng)目,并將其稱為“ selenuim-tutorial”。
要運(yùn)行集成測試(IT),我們將使用Cargo插件。 這將啟動(dòng)和停止Jetty和Tomcat之類的容器。 您可以使用Cargo在一個(gè)命令中使用Jetty(默認(rèn)設(shè)置)啟動(dòng)網(wǎng)站,而無需進(jìn)行任何更改:
mvn cargo:run并在瀏覽器中檢查它:
http:// localhost:8080 / selenuim-tutorial
您將獲得一個(gè)沒有歡迎文件設(shè)置的404,因此將其添加到web.xml文件中:
<welcome-file-list><welcome-file>/index.jsp</welcome-file> </welcome-file-list>如果您運(yùn)行貨物:再次運(yùn)行,您現(xiàn)在將看到“ Hello World!” 由Maven創(chuàng)建的頁面。
配置貨物
我們可以將Cargo設(shè)置為在運(yùn)行測試之前啟動(dòng)Jetty容器,然后再將其停止。 這將使我們能夠啟動(dòng)站點(diǎn),運(yùn)行集成測試,然后再將其停止。
<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>您可以使用以下方法測試這項(xiàng)工作:
mvn verify此時(shí)要注意的一件事是Cargo運(yùn)行在端口8080上。如果您已經(jīng)有一個(gè)進(jìn)程在該端口上進(jìn)行偵聽,則可能會(huì)看到類似以下錯(cuò)誤:
java.net.BindException: Address already in use這可能是因?yàn)槟呀?jīng)在此端口上運(yùn)行了另一個(gè)容器。 如果要在CI上運(yùn)行它(它本身可以在端口8080上運(yùn)行),則可能需要更改。 將這些行添加到插件設(shè)置中:
<configuration><type>standalone</type><configuration><properties><cargo.servlet.port>10001</cargo.servlet.port></properties></configuration> </configuration>現(xiàn)在該應(yīng)用程序?qū)⒃谶@里:
http:// localhost:10001 / selenuim-tutorial /
設(shè)置集成測試階段
接下來,我們需要能夠運(yùn)行集成測試。 這需要Maven故障安全插件,并將適當(dāng)?shù)哪繕?biāo)添加到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>默認(rèn)情況下,Failsafe期望測試匹配模式“ src / test / java / * / * IT.java”。 讓我們創(chuàng)建一個(gè)測試來證明這一點(diǎn)。 請(qǐng)注意,我尚未從Junit 3.8.1更改過。 稍后我將解釋原因。
這是一個(gè)基本的,不完整的測試:
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您應(yīng)該看到一個(gè)測試失敗。
要使用Selenium進(jìn)行測試,您需要向pom.xml添加測試范圍的依賴項(xiàng):
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-firefox-driver</artifactId><version>2.19.0</version><scope>test</scope> </dependency>現(xiàn)在,我們可以對(duì)測試進(jìn)行一些更改:
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"));}稍后我們將刪除這些硬編碼值。
再次運(yùn)行:
mvn verify您應(yīng)該不會(huì)看到任何故障。 您將擁有一個(gè)揮之不去的Firefox。 它不會(huì)關(guān)閉。 運(yùn)行此測試100次,您將運(yùn)行100個(gè)Firefox。 這將很快成為一個(gè)問題。 我們可以通過在測試中添加以下初始化塊來解決此問題:
{Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic void run() {drv.close();}});}自然,如果我們創(chuàng)建另一個(gè)測試,我們很快就會(huì)違反DRY原則。 我們將在下一部分中討論該問題,并查看需要數(shù)據(jù)庫連接時(shí)發(fā)生的情況,以及其他一些方法來確保您的測試易于編寫和維護(hù)。
Spring語境
在前面的示例中,應(yīng)用程序的URI和使用的驅(qū)動(dòng)程序都經(jīng)過了硬編碼。 假設(shè)您熟悉Spring上下文,那么更改這些內(nèi)容很簡單。 首先,我們將添加正確的依賴項(xiàng):
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>3.1.1.RELEASE</version><scope>test</scope> </dependency>這將使我們能夠使用和應(yīng)用程序上下文來注入依賴項(xiàng)。 但是我們還需要正確的Junit運(yùn)行程序來測試它,可以在spring-test軟件包中找到它:
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>3.1.1.RELEASE</version><scope>test</scope> </dependency>現(xiàn)在,我們可以更新測試以使用它。 首先,我們需要?jiǎng)?chuàng)建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中刪除關(guān)閉鉤子。 這比讓測試用例執(zhí)行此操作更為健壯。
彈簧測試不適用于JUnit 3,它至少需要JUnit 4.5。 讓我們?cè)趐om.xml中更新到版本4.10:
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version><scope>test</scope> </dependency>最后,我們需要更新測試以同時(shí)使用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() { ...這些更改將配置從硬編碼值轉(zhuǎn)移到XML配置。 現(xiàn)在,我們可以將要測試的位置更改為例如其他主機(jī),并更改我們正在使用的Web驅(qū)動(dòng)程序,這留給用戶練習(xí)。
關(guān)于瀏覽器的快速說明。 我發(fā)現(xiàn)瀏覽器更新后,測試通常會(huì)開始失敗。 似乎有兩種解決方案:
- 升級(jí)到最新版本的Web驅(qū)動(dòng)程序。
- 不要升級(jí)瀏覽器。
我出于安全考慮,在大多數(shù)情況下,我認(rèn)為第一種選擇是最好的
抽象IT
當(dāng)前,您需要復(fù)制IoC的所有代碼。 一個(gè)簡單的重構(gòu)可以解決這個(gè)問題。 我們將為所有測試和上拉通用功能創(chuàng)建一個(gè)超類。 出于重構(gòu)原因,我將使用繼承而不是合成,原因?qū)⒃谏院蠼榻B。
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"));} }頁面對(duì)象
“頁面對(duì)象”是封裝頁面的單個(gè)實(shí)例并為該實(shí)例提供程序化API的對(duì)象。 基本頁面可能是:
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;} }請(qǐng)注意,我提供了一個(gè)靜態(tài)方法來返回我們是否在索引頁上,并且已經(jīng)對(duì)其進(jìn)行了注釋(對(duì)于這樣的自填充方法,這是不必要的)。 頁面對(duì)象形成一個(gè)API,值得記錄。 您還將看到,如果URL不正確,我們將引發(fā)異常。 值得考慮使用什么條件來識(shí)別頁面。 任何可能更改的內(nèi)容(例如,頁面標(biāo)題,可能會(huì)在不同語言之間更改)都是一個(gè)糟糕的選擇。 不變的東西和機(jī)器可讀的東西(例如頁面的路徑)是不錯(cuò)的選擇。 如果要更改路徑,則需要更改測試。
現(xiàn)在讓我們自己制造一個(gè)問題。 我想將其添加到index.jsp,但是生成HTML無法解析:
<% throw new RuntimeException(); %>相反,我們將創(chuàng)建一個(gè)新的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并刪除現(xiàn)在不必要的歡迎頁面:
<!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());}再次運(yùn)行測試。 它通過了。 這可能不是您想要的行為。 Selenium沒有提供通過WebDriver實(shí)例檢查HTTP狀態(tài)代碼的方法。 容器之間的默認(rèn)錯(cuò)誤頁面也沒有足夠一致(例如,與在Tomcat上運(yùn)行時(shí)進(jìn)行比較); 我們無法對(duì)錯(cuò)誤頁面的內(nèi)容進(jìn)行假設(shè)以判斷是否發(fā)生錯(cuò)誤。
我們的索引頁面當(dāng)前沒有任何可機(jī)讀的功能,可以讓我們從錯(cuò)誤頁面中分辨出來。
要整理,請(qǐng)修改servlet以顯示index.jsp:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {getServletContext().getRequestDispatcher("/index.jsp").forward(request, response);}當(dāng)前index.jsp有點(diǎn)太簡單了。 在index.jsp旁邊創(chuàng)建一個(gè)名為create-order.jsp的新頁面,并在index.jsp上創(chuàng)建指向該頁面的鏈接。 我們可以為訂單頁面創(chuàng)建一個(gè)新類,并創(chuàng)建一個(gè)將我們從索引頁面導(dǎo)航到訂單頁面的方法。
將以下內(nèi)容添加到index.jsp中:
<a href="create-order.jsp">Create an order</a>create-order.jsp現(xiàn)在可以為空。 我們還可以為其創(chuàng)建一個(gè)頁面對(duì)象:
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;} }將以下依賴項(xiàng)添加到pom.xml中,這將為我們提供一些有用的注釋:
<dependency><groupId>org.seleniumhq.selenium</groupId><artifactId>selenium-support</artifactId><version>2.19.0</version><scope>test</scope> </dependency>我們現(xiàn)在可以充實(shí)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;}對(duì)PageFactory.initElements的調(diào)用將填充帶有@FindBy注釋的字段,該字段具有與網(wǎng)頁上的元素匹配的對(duì)象。 請(qǐng)注意,使用CSS選擇器是為了以不太可能更改的方式定位鏈接。 其他方法包括使用鏈接文本來匹配頁面上的元素(可能會(huì)因不同的語言而改變)。
現(xiàn)在,我們可以在IndexPages上創(chuàng)建一個(gè)方法,該方法導(dǎo)航到CreateOrderPages。
public CreateOrderPage createOrder() {createOrderLink.click();return new CreateOrderPage(drv, siteBase);}最后,我們可以在IndexPageIT中為此鏈接創(chuàng)建一個(gè)測試:
@Testpublic void testCreateOrder() {getDrv().get(getSiteBase().toString());new IndexPage(getDrv(), getSiteBase()).createOrder();assertTrue(CreateOrderPage.isAtCreateOrderPage(getDrv(), getSiteBase()));}執(zhí)行mvn verify,您應(yīng)該找到新的測試通過。 至此,我們有兩個(gè)測試無法在它們之間進(jìn)行清理。 他們?cè)趦蓚€(gè)測試中使用相同的WebDriver實(shí)例,最后一頁仍將打開,并且設(shè)置的所有cookie都將保持不變。 為多個(gè)測試創(chuàng)建單個(gè)WebDriver實(shí)例的優(yōu)缺點(diǎn)。 主要優(yōu)點(diǎn)是減少了打開和關(guān)閉瀏覽器的時(shí)間成本,但是一個(gè)缺點(diǎn)是,每次測試,Cookie設(shè)置和彈出窗口打開后,瀏覽器實(shí)際上都會(huì)變臟。 我們可以使用AbstractIT中合適的setUp方法確保每次測試之前它都是干凈的:
@Beforepublic void setUp() {getDrv().manage().deleteAllCookies();getDrv().get(siteBase.toString());}有其他方法可以解決,我將讓您自行研究在每次測試之前創(chuàng)建新的WebDriver實(shí)例的方法。
@FindBy批注在窗體上使用時(shí)特別有用。 向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);}最后,我們可以為此創(chuàng)建一個(gè)測試:
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");} }結(jié)論
您可能要注意的一件事是,submit方法不需要將金額作為您期望的數(shù)字。 您可以創(chuàng)建一個(gè)測試以查看提交的是字符串而不是數(shù)字。 集成測試的編寫可能很耗時(shí),并且由于諸如元素ID或輸入名稱之類的事物的更改而容易損壞。 結(jié)果,創(chuàng)建它們所獲得的最大好處是,最初僅在您站點(diǎn)內(nèi)的業(yè)務(wù)關(guān)鍵路徑上創(chuàng)建它們,例如,產(chǎn)品訂購,客戶注冊(cè)流程和付款。
在本教程的下一部分中,我們將研究如何使用一些數(shù)據(jù)支持測試以及由此帶來的挑戰(zhàn)。
參考:我們的JCG合作伙伴 Alex Collins在Alex Collins博客上的教程 : 教程:與Selenium的集成測試-第1部分 , 教程:與Selenium的集成測試-第2部分 。
翻譯自: https://www.javacodegeeks.com/2012/04/integration-testing-with-selenium.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的与Selenium的集成测试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 发现大量Java原语集合处理
- 下一篇: 本机速度文件支持的“纯” Java大数据