使用 WebDriver 进行 Spring MVC 测试

工程 | Rob Winch | 2014 年 3 月 26 日 | ...

在我的第二篇文章中,我描述了如何使用 HtmlUnit 进行 Spring MVC 测试。在本文中,我们将利用 WebDriver 中的额外抽象来使事情变得更加容易。

为何选择 WebDriver?

我们已经可以使用 HtmlUnit 和 MockMvc,那么为何还要使用 WebDriver 呢?WebDriver 提供了非常优雅的 API,并允许我们轻松组织代码。为了更好地理解,我们来探索一个例子。


注意 尽管是 Selenium 的一部分,WebDriver 运行测试时不需要 Selenium Server。


假设我们需要确保消息被正确创建。测试包括查找 HTML 输入框、填写它们,并进行各种断言。

测试有很多,因为我们也要测试错误条件。例如,我们要确保如果我们只填写表单的一部分,会得到错误。如果我们填写整个表单,新创建的消息会随后显示。

如果其中一个字段名为 "summary",那么在我们的测试中可能到处重复出现类似以下的代码

HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");

那么如果我们把 id 改成 "smmry" 会怎样呢?这意味着我们将不得不更新我们所有的测试!相反,我们希望我们编写的代码更优雅一些,将填写表单的逻辑放在一个单独的方法中

public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
  ...
  setSummary(currentPage, summary);
  ...
}

public void setSummary(HtmlPage currentPage, String summary) {
  HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
  summaryInput.setValueAttribute(summary);
}

这确保了如果我们更改 UI,就不必更新所有测试。

我们可能会更进一步,将此逻辑放在一个代表我们当前所在的 HtmlPage 的对象中。

public class CreateMessagePage {
  private final HtmlPage currentPage;

  ...

  public T createMessage(Class<T> resultPage, String summary, String text) {
    ...
    setSummary(currentPage, summary);
    ...
    HtmlPage result = submit.click();
    ...
    return (T) error ? new CreateMessagePage(result) : new ViewMessagePage(result);
  }

  public void setSummary(String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
  }
}

以前,这种模式被称为 Page Object 模式。虽然我们当然可以使用 HtmlUnit 实现此模式,但 WebDriver 提供了一些工具,我们将在后续章节中探索这些工具,使此模式变得更加容易。

更新依赖

在使用项目之前,您必须确保更新您的依赖。关于 MavenGradle 的说明可以在网站文档中找到。

使用 WebDriver

现在我们有了正确的依赖,我们可以在单元测试中使用 WebDriver。我们的示例假设您已经将 JUnit 作为依赖。如果尚未添加,请相应地更新您的 classpath。使用 WebDriver 和 Spring MVC Test 的完整代码示例可以在 MockMvcHtmlUnitDriverCreateMessageTests 中找到。

创建 MockMvc

为了使用 WebDriver 和 Spring MVC Test,我们首先必须创建一个 MockMvc 实例。关于如何创建 MockMvc 实例有很多文档,但我们将在本节中非常快速地回顾如何创建一个 MockMvc 实例。

第一步是创建一个新的 JUnit 类,并按照如下所示添加注解

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebMvcConfig.class, MockDataConfig.class})
@WebAppConfiguration
public class MockMvcHtmlUnitDriverCreateMessageTests {

  @Autowired
  private WebApplicationContext context;

  ...
}
  • @RunWith(SpringJUnit4ClassRunner.class) 允许 Spring 对我们的 MockMvcHtmlUnitDriverCreateMessageTests 进行依赖注入。这就是为什么我们的 @Autowired 注解会生效的原因。
  • @ContextConfiguration 告诉 Spring 要加载哪些配置。您会注意到我们加载了一个模拟的数据层实例,以提高测试性能。如果愿意,我们可以选择针对真实数据库运行测试。但是,这会带来我们之前提到的缺点
  • @WebAppConfigurationSpringJUnit4ClassRunner 表明它应该创建一个 WebApplicationContext 而不是 ApplicationContext

接下来,我们需要从 context 创建我们的 MockMvc 实例。下面提供了如何执行此操作的示例

@Before
public void setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
  ...  
}

当然,这只是创建 MockMvc 实例的一种方法。我们可以选择添加 Servlet Filter,使用独立设置等。重要的是我们需要一个 MockMvc 实例。有关创建 MockMvc 实例的更多信息,请参阅 Spring MVC Test 文档

初始化 WebDriver

现在我们已经创建了 MockMvc 实例,我们需要创建一个 MockMvcHtmlUnitDriver,它确保我们使用上一步创建的 MockMvc 实例。

private WebDriver driver;

@Before
public void setup() {
	MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
	driver = new MockMvcHtmlUnitDriver(mockMvc, true);
}

使用 WebDriver

现在我们可以像往常一样使用 WebDriver,而无需部署我们的应用程序。例如,我们可以使用以下方法请求创建消息的视图

CreateMessagePage messagePage = CreateMessagePage.to(driver);

然后我们可以填写表单并提交以创建消息。

ViewMessagePage viewMessagePage = 
    messagePage.createMessage(ViewMessagePage.class, expectedSummary, expectedText);

通过利用 Page Object 模式,这改进了我们的 HtmlUnit 测试的设计。正如我们在为何选择 WebDriver?中提到的,我们可以使用 HtmlUnit 来使用 Page Object 模式,但现在使用 WebDriver 会容易得多。让我们看看我们的 CreateMessagePage

public class CreateMessagePage extends AbstractPage {
    private WebElement summary;

    private WebElement text;

    @FindBy(css = "input[type=submit]")
    private WebElement submit;

    public CreateMessagePage(WebDriver driver) {
        super(driver);
    }

    public <T> T createMessage(Class<T> resultPage, String summary, String details) {
        this.summary.sendKeys(summary);
        this.text.sendKeys(details);
        this.submit.click();
        return PageFactory.initElements(driver, resultPage);
    }

    public static CreateMessagePage to(WebDriver driver) {
        driver.get("http://localhost:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}

您会注意到的第一件事是我们的 CreateMessagePage 扩展了 AbstractPage。我们不会详细介绍 AbstractPage 的细节,但总而言之,它包含我们所有页面的所有通用功能。例如,如果您的应用程序有导航栏、全局错误消息等,可以将此逻辑放在共享位置。

接下来您会发现我们对我们感兴趣的 HTML 各部分,即 WebElement,都有一个成员变量。WebDriverPageFactory 允许我们通过自动解析每个 WebElement 来减少 HtmlUnit 版本 CreateMessagePage 中的大量代码。

PageFactory#initElements 方法将使用字段名称自动解析每个 WebElement,并尝试通过 HTML 页面上元素的 id 或 name 来查找它。我们还可以使用 @FindBy 注解来覆盖默认设置。我们的示例演示了如何使用 @FindBy 注解通过 css 选择器 input[type=submit] 来查找提交按钮。

最后,我们可以验证新消息是否成功创建

assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");

我们可以看到,我们的 ViewMessagePage 除了单独的 Message 属性外,还可以返回一个 Message 对象。这使我们可以轻松地与富领域对象交互,而不仅仅是 String。然后,我们可以在断言中利用富领域对象。我们通过创建一个 自定义 fest 断言来实现这一点,该断言允许我们验证实际 Message 的所有属性是否等于预期的 Message。您可以在 AssertionsMessageAssert 中查看自定义断言的详细信息。

最后,完成后别忘了关闭 WebDriver 实例。

@After
public void destroy() {
	if(driver != null) {
		driver.close();
	}
}

有关使用 WebDriver 的更多信息,请参阅 WebDriver 文档

一切都变得 Groovy...

WebDriver 拥有使用 HtmlUnit 的所有优点,并且额外增加了对 Page Object 模式的轻松支持。然而,仍然存在相当多的样板代码可以改进。在我们的下一篇文章中,我们将看到如何使用 Geb 让我们的测试更加 Groovy。


请提供反馈!

如果您对本博客系列或 Spring Test MVC HtmlUnit 有任何反馈,欢迎通过 GitHub Issues 联系我,或在 Twitter 上 @我 @rob_winch。当然,最好的反馈是贡献

订阅 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,仅需一份简单订阅。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部