领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在我 第二篇文章中,我介绍了如何使用 Spring MVC Test 配合 HtmlUnit。在这篇文章中,我们将利用 WebDriver 中的额外抽象来使事情变得更加容易。
我们已经可以使用 HtmlUnit 和 MockMvc 了,那为什么还要使用 WebDriver 呢?WebDriver 提供了一个非常优雅的 API,并允许我们轻松地组织代码。为了更好地理解,让我们来看一个例子。
注意:尽管 WebDriver 是 Selenium 的一部分,但它并不需要 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 Pattern。虽然我们可以肯定地使用 HtmlUnit 来实现这一点,但 WebDriver 提供了一些工具,我们将在接下来的章节中探讨,使这种模式更加容易。
在使用该项目之前,您必须确保更新您的依赖项。可以在网站文档中找到 Maven 和 Gradle 的说明。
现在我们有了正确的依赖项,我们可以在单元测试中使用 WebDriver。我们的示例假定您已经将 JUnit 作为依赖项。如果您尚未添加,请相应地更新您的类路径。使用 WebDriver 和 Spring MVC Test 的完整代码示例可以在 MockMvcHtmlUnitDriverCreateMessageTests 中找到。
为了使用 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加载哪个配置。您会注意到我们正在加载一个模拟的数据库层实例以提高测试性能。如果我们愿意,也可以选择在真实数据库上运行测试。然而,这存在我们之前提到过的缺点。@WebAppConfiguration 指示 SpringJUnit4ClassRunner 创建一个 WebApplicationContext 而不是 ApplicationContext。接下来,我们需要从context创建我们的MockMvc实例。下面提供了一个如何做到这一点的示例
@Before
public void setup() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
...
}
当然,这只是创建MockMvc实例的一种方式。我们也可以选择添加Servlet Filter,使用Standalone setup等。重要的是我们需要一个MockMvc的实例。有关创建MockMvc实例的更多信息,请参阅Spring MVC Test文档。
现在我们已经创建了 MockMvc 实例,我们需要创建一个 MockMvcHtmlUnitDriver,它确保我们使用上一步中创建的 MockMvc 实例。
private WebDriver driver;
@Before
public void setup() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
driver = new MockMvcHtmlUnitDriver(mockMvc, true);
}
现在我们可以像平常一样使用 WebDriver,而无需部署我们的应用程序。例如,我们可以使用以下方式请求创建消息的视图:
CreateMessagePage messagePage = CreateMessagePage.to(driver);
然后,我们可以填写表单并提交以创建消息。
ViewMessagePage viewMessagePage =
messagePage.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
这通过利用 Page Object Pattern 改进了我们的 HtmlUnit 测试。正如我们在 为什么使用 WebDriver? 中提到的,我们可以使用 Page Object Pattern 与 HtmlUnit,但现在它要容易得多。让我们看看我们的 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("https://:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
您首先会注意到的是,我们的 CreateMessagePage 扩展了 AbstractPage。我们不会详细介绍 AbstractPage 的细节,但总而言之,它包含了我们所有页面的所有通用功能。例如,如果您的应用程序有导航栏、全局错误消息等。此逻辑可以放置在一个共享位置。
接下来您会发现,我们为 HTML 的每个部分(WebElement)都创建了一个成员变量。WebDriver 的 PageFactory 允许我们通过自动解析每个 WebElement 来移除 HtmlUnit 版 CreateMessagePage 中的大量代码。
PageFactory#initElements 方法将使用字段名并通过查找 HTML 页面上的元素 ID 或名称来自动解析每个 WebElement。我们也可以使用 @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 相等。您可以在 Assertions 和 MessageAssert 中查看自定义断言的详细信息。
最后,别忘了在完成后关闭 WebDriver 实例。
@After
public void destroy() {
if(driver != null) {
driver.close();
}
}
有关使用 WebDriver 的更多信息,请参阅 WebDriver 文档。
WebDriver 具有使用 HtmlUnit 的相同优点,并增加了对使用 Page Object 模式的轻松支持。然而,这其中有相当多的样板代码可以改进。在下一篇文章中,我们将看到如何使用 Geb 来使我们的测试更加 Groovy。
欢迎反馈!
如果您对本系列博客或Spring Test MVC HtmlUnit有任何反馈,我鼓励您通过github issues与我联系,或在twitter上@rob_winch。当然,最好的反馈是贡献。