|
对于最新稳定版本,请使用Spring Framework 7.0.1! |
为什么要HtmlUnit集成?
最明显的问题是“我为什么需要这个?”答案是
最好通过探索一个非常基础的示例应用来找到。假设你有一个春季MVC网页
支持在消息对象。应用内容也
支持分页处理所有消息。你会怎么测试它?
通过 Spring MVC 测试,我们可以轻松测试是否能够创建消息如下:
-
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>
我们如何确保表单生成正确的请求来创建新消息?一个 天真的尝试可能类似于以下情况:
-
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() }
}
这个测试有一些明显的缺点。如果我们更新控制器使用参数消息而不是文本,我们的表单测试仍然通过,尽管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 表达式,但随着考虑更多因素,表达式会变得更复杂:是 字段类型正确吗?这些字段是启用的吗?诸如此类。
-
另一个问题是,我们做的工作量是预期的两倍。我们必须先 验证视图,然后我们提交使用刚验证的相同参数的视图。 理想情况下,这一切可以一次性完成。
-
最后,我们仍然无法解释一些情况。例如,如果 形式 有 我们也想测试的JavaScript验证?
总体问题在于,测试网页并不涉及单一交互。 相反,它是用户如何与网页互动以及网页本身的结合 页面与其他资源互动。例如,表单视图的结果被使用 用户创建消息时的输入。此外,我们的表单视图可能 使用影响页面行为的额外资源,如JavaScript 验证。
集成测试来救场?
为了解决前面提到的问题,我们可以进行端到端集成测试, 但这也有一些缺点。考虑测试那个让我们翻页的视图 消息。我们可能需要以下测试:
-
我们的页面会不会向用户显示通知,表示没有结果 消息空了还能用吗?
-
我们的页面是否正确显示一条信息?
-
我们的页面是否真正支持分页?
为了设置这些测试,我们需要确保数据库包含正确的消息。这 这带来了许多额外的挑战:
-
确保数据库中正确的消息可能很繁琐。(考虑外键 约束。)
-
测试可能会变慢,因为每次测试都需要确保数据库已经在 正确的状态。
-
由于数据库需要处于特定状态,我们无法并行运行测试。
-
对自动生成的ID、时间戳等项执行断言可以 要难搞。
这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以通过以下方式减少端到端集成测试的数量 重构我们详细的测试,使用运行更快、更可靠的模拟服务, 而且没有副作用。然后我们可以实现少量真正的端到端 验证简单工作流程的集成测试,确保所有工作过程协同工作 适当地。
HtmlUnit 集成选项
当你想将MockMvc与HtmlUnit集成时,有多种选择:
-
MockMvc 和 HtmlUnit:如果你 想使用原始的 HtmlUnit 库。
-
MockMvc 和 WebDriver:使用此选项 简化集成与端到端测试之间的代码开发和重用。
-
MockMvc 和 Geb:如果你想用这个选项 使用Groovy进行测试,简化开发,并在集成和 端到端测试。