对于最新稳定版本,请使用Spring Framework 7.0.1spring-doc.cadn.net.cn

为什么要HtmlUnit集成?

最明显的问题是“我为什么需要这个?”答案是 最好通过探索一个非常基础的示例应用来找到。假设你有一个春季MVC网页 支持在消息对象。应用内容也 支持分页处理所有消息。你会怎么测试它?spring-doc.cadn.net.cn

通过 Spring MVC 测试,我们可以轻松测试是否能够创建消息如下:spring-doc.cadn.net.cn

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")
	}
}

如果我们想测试能创建消息的表单视图呢?例如 假设我们的表格如下片段:spring-doc.cadn.net.cn

<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>

我们如何确保表单生成正确的请求来创建新消息?一个 天真的尝试可能类似于以下情况:spring-doc.cadn.net.cn

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表单 与手柄不同步。为解决此问题,我们可以将两个检验结合为 遵循:spring-doc.cadn.net.cn

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")
}

这样可以降低我们考试错误通过的风险,但仍有一些问题 问题:spring-doc.cadn.net.cn

  • 如果我们的页面上有多个表单怎么办?诚然,我们可以更新我们的XPath 表达式,但随着考虑更多因素,表达式会变得更复杂:是 字段类型正确吗?这些字段是启用的吗?诸如此类。spring-doc.cadn.net.cn

  • 另一个问题是,我们做的工作量是预期的两倍。我们必须先 验证视图,然后我们提交使用刚验证的相同参数的视图。 理想情况下,这一切可以一次性完成。spring-doc.cadn.net.cn

  • 最后,我们仍然无法解释一些情况。例如,如果 形式 有 我们也想测试的JavaScript验证?spring-doc.cadn.net.cn

总体问题在于,测试网页并不涉及单一交互。 相反,它是用户如何与网页互动以及网页本身的结合 页面与其他资源互动。例如,表单视图的结果被使用 用户创建消息时的输入。此外,我们的表单视图可能 使用影响页面行为的额外资源,如JavaScript 验证。spring-doc.cadn.net.cn

集成测试来救场?

为了解决前面提到的问题,我们可以进行端到端集成测试, 但这也有一些缺点。考虑测试那个让我们翻页的视图 消息。我们可能需要以下测试:spring-doc.cadn.net.cn

为了设置这些测试,我们需要确保数据库包含正确的消息。这 这带来了许多额外的挑战:spring-doc.cadn.net.cn

这些挑战并不意味着我们应该放弃端到端集成测试 完全。相反,我们可以通过以下方式减少端到端集成测试的数量 重构我们详细的测试,使用运行更快、更可靠的模拟服务, 而且没有副作用。然后我们可以实现少量真正的端到端 验证简单工作流程的集成测试,确保所有工作过程协同工作 适当地。spring-doc.cadn.net.cn

HTMLUnit 集成应运而生

那么,我们如何在测试页面交互和仍然保持平衡之间取得平衡 在我们的测试套件中保持良好性能?答案是:“通过整合MockMvc 使用HtmlUnit。”spring-doc.cadn.net.cn

HtmlUnit 集成选项

当你想将MockMvc与HtmlUnit集成时,有多种选择:spring-doc.cadn.net.cn