与Selenium的集成测试

总览

我已经使用了一段时间,并且遇到了一些似乎可以使生活更轻松的事情。 我以为可以将其作为教程分享,所以我将向您介绍这些部分:

  1. 使用Maven设置Web项目,配置Selenium以在CI上作为集成测试运行
  2. 研究使用“页面对象”为网站中的页面建模的好方法,以及创建受保护的变量的其他方法。
  3. 使用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 {
 
 @Override
 protected void setUp() throws Exception {
  super.setUp();
 }
 
 @Override
 protected 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;
 
 @Override
 protected 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() {
   @Override
   public 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/beans
           http://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 {
 
 @Autowired
 private URI siteBase;
 
 @Autowired
 private WebDriver drv;
 
 @Test
 public 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 {
 
 @Autowired
 private URI siteBase;
 
 @Autowired
 private 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 {
 
 @Test
 public 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:

@Test
 public 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中为此链接创建一个测试:

@Test
 public void testCreateOrder() {
  getDrv().get(getSiteBase().toString());
 
  new IndexPage(getDrv(), getSiteBase()).createOrder();
 
  assertTrue(CreateOrderPage.isAtCreateOrderPage(getDrv(), getSiteBase()));
 }

执行mvn verify,您应该找到新的测试通过。 至此,我们有两个测试无法在它们之间进行清理。 他们在两个测试中使用相同的WebDriver实例,最后一页仍将打开,并且设置的所有cookie都将保持不变。 为多个测试创建单个WebDriver实例的优缺点。 主要优点是减少了打开和关闭浏览器的时间成本,但是一个缺点是,每次测试,Cookie设置和弹出窗口打开后,浏览器实际上都会变脏。 我们可以使用AbstractIT中合适的setUp方法确保每次测试之前它都是干净的:

@Before
 public 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 {
 
 @Test
 public void testSubmit() {
  new IndexPage(getDrv(), getSiteBase()).createOrder().submit("foo", "1.0");
 }
}

结论

您可能要注意的一件事是,submit方法不需要将金额作为您期望的数字。 您可以创建一个测试以查看提交的是字符串而不是数字。 集成测试的编写可能很耗时,并且由于诸如元素ID或输入名称之类的事物的更改而容易损坏。 结果,创建它们所获得的最大好处是,最初仅在您站点内的业务关键路径上创建它们,例如,产品订购,客户注册流程和付款。

在本教程的下一部分中,我们将研究如何使用一些数据支持测试以及由此带来的挑战。

参考:我们的JCG合作伙伴 Alex Collins在Alex Collins博客上的教程 教程:与Selenium的集成测试-第1部分教程:与Selenium的集成测试-第2部分


翻译自: https://www.javacodegeeks.com/2012/04/integration-testing-with-selenium.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值