此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
为什么选择 HtmlUnit 集成?
我想到的最明显的问题是“我为什么需要这个?答案是
最好通过浏览一个非常基本的示例应用程序来找到。假设您有一个 Spring MVC Web
支持在Message
对象。该应用程序还
支持对所有消息进行分页。您将如何进行测试?
使用 Spring MVC Test,我们可以轻松测试是否能够创建一个Message
如下:
-
Java
-
Kotlin
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
@Test
fun test() {
mockMvc.post("/messages/") {
param("summary", "Spring Rocks")
param("text", "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
}
如果我们想测试允许我们创建消息的表单视图,该怎么办?例如 假设我们的表单类似于以下代码段:
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
我们如何确保我们的表单生成正确的请求来创建新消息?一个 Naïve Attempt 可能类似于以下内容:
-
Java
-
Kotlin
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='summary']") { exists() }
xpath("//textarea[@name='text']") { exists() }
}
此测试有一些明显的缺点。如果我们更新控制器以使用参数message
而不是text
,我们的表单测试将继续通过,即使 HTML 表单
与控制器不同步。为了解决这个问题,我们可以将两个测试结合起来,因为
遵循:
-
Java
-
Kotlin
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='$summaryParamName']") { exists() }
xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
param(summaryParamName, "Spring Rocks")
param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
这将降低我们的测试错误通过的风险,但仍有一些 问题:
-
如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的 XPath 表达式,但随着我们考虑更多因素,它们会变得更加复杂:Are 字段类型正确?字段是否已启用?等等。
-
另一个问题是,我们所做的工作是预期的两倍。我们必须首先 验证视图,然后我们使用刚刚验证的相同参数提交视图。 理想情况下,可以一次性完成此作。
-
最后,我们仍然无法解释一些事情。例如,如果表单具有 我们也希望测试的 JavaScript 验证?
总体问题是测试 Web 页面不涉及单个交互。 相反,它是用户如何与网页交互以及该 Web 如何交互的组合 page 与其他资源交互。例如,表单视图的结果用作 用于创建消息的用户的输入。此外,我们的表单视图可能会 使用影响页面行为的其他资源,例如 JavaScript 验证。
集成测试来救援?
为了解决前面提到的问题,我们可以执行端到端的集成测试, 但这有一些缺点。考虑测试允许我们分页浏览 消息。我们可能需要以下测试:
-
我们的页面是否向用户显示通知以指示没有结果 当消息为空时可用?
-
我们的页面是否正确显示单个消息?
-
我们的页面是否正确支持分页?
要设置这些测试,我们需要确保我们的数据库包含正确的消息。这 导致许多其他挑战:
-
确保数据库中有正确的消息可能很乏味。(考虑外键 约束。
-
测试可能会变慢,因为每个测试都需要确保数据库处于 正确的状态。
-
由于我们的数据库需要处于特定状态,因此我们不能并行运行测试。
-
对自动生成的 ID、时间戳等项目执行断言可以 要困难。
这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以将端到端集成测试的数量减少 重构我们的详细测试以使用运行更快、更可靠的 Mock 服务, 并且没有副作用。然后,我们可以实现少量的真正的端到端 集成测试,验证简单的工作流程,以确保一切协同工作 适当地。
HtmlUnit 集成选项
当您想将 MockMvc 与 HtmlUnit 集成时,您有许多选择:
-
MockMvc 和 HtmlUnit:如果满足以下条件,请使用此选项 想要使用原始 HtmlUnit 库。
-
MockMvc 和 WebDriver:使用此选项可以 简化开发并在集成和端到端测试之间重用代码。
-
MockMvc 和 Geb:如果需要,请使用此选项 使用 Groovy 进行测试,简化开发,并在集成和 端到端测试。