提高集成测试的能见度
扫描二维码
随时随地手机看文章
在现代软件开发中,有效的测试在确保应用程序的可靠性和稳定性方面发挥着关键作用。
本文为编写集成测试提供了实用的建议,演示了如何侧重于与外部服务的交互规范,使测试更具可读性和易于维护。该方法不仅提高了测试的效率,而且还促进了对应用程序中集成流程的更好理解。通过具体例子,各种策略和工具,例如DSL包装器,JsonAssert ,并将探索PACT-将为读者提供一个全面的指南,以提高整合测试的质量和能见度。
本文给出了使用斯波克框架进行集成测试的例子,用于测试春季应用程序中的http交互。与此同时,它所建议的主要技术和方法可以有效地应用于除了HTTP之外的各种类型的相互作用。
问题说明
文章 订单混乱:在春季安排TP请求测试 描述编写测试的方法,明确地将其划分为不同的阶段,每个阶段都执行其特定的角色。让我们根据这些建议描述一个测试示例,但是要用一个而不是两个请求来嘲笑。为简单起见,行为阶段(执行)将被省略(完整的测试示例可在 项目存放处 ).
该代码有条件地分为两个部分:"支持代码"(灰色)和"外部交互的规范"(蓝色)。支持代码包括测试的机制和实用程序,包括拦截请求和模拟响应。外部交互的规范描述了系统在测试期间应该与之交互的外部服务的具体数据,包括预期的请求和响应。支持代码为测试奠定了基础,而规范直接关系到我们试图测试的系统的业务逻辑和主要功能。
规范占用了代码的一小部分,但在理解测试方面具有重要价值,而占用更大部分的支持代码则显示较少的价值,而且每个模拟声明都是重复的。本守则拟於MockRestServiceServer .提及 威雷莫克的例子 ,你可以看到相同的模式:规范几乎相同,支持代码也各不相同。
本文的目的是为编写测试提供实用的建议,这样的方式将重点放在规范上,而支持代码将占次要位置。
演示场景
对于我们的测试场景,我提出了一个假设的电报机器人,它将请求转发到开放的API并将响应发送给用户。
以简化的方式描述了与服务交互的合同,以突出业务的主要逻辑。下面是展示应用程序体系结构的序列图。我理解,设计可能会从系统架构的角度提出问题,但请理解这一点--这里的主要目标是演示一种提高测试可见性的方法。
提议
本文讨论了以下关于写作测试的实用建议:
· 使用DSL包装器进行模拟操作
· 使用JsonAssert 结果核实
· 在JSON文件中存储外部交互的规范
· 使用契约文件
使用DSL包装器来嘲笑
使用DSL包装器可以隐藏模板模拟代码,并提供一个简单的接口与规范工作。重要的是要强调的是,所提出的并不是一个特定的DSL,而是它所实现的一种一般方法。以下是使用DSL校正测试的例子( 完整测试文本 ).
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess("{...}"))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
在那里方法 restExpectation.openai.completions 例如,其描述如下:
public interface OpenaiMock {
/**
* This method configures the mock request to the following URL: {@code https://api.openai.com/v1/chat/completions}
*/
RequestCaptor completions(DefaultResponseCreator responseCreator);
}
在代码编辑器中对方法进行注释时,可以获得帮助,包括查看将被嘲笑的URL。
在拟议的实施中,模拟答复的声明使用的是 应答者 允许定制的实例,例如:
public static ResponseCreator withResourceAccessException() {
return (request) -> {
throw new ResourceAccessException("Error");
};
}
下面是对不成功情景的一个例子测试,具体说明了一组响应:
import static org.springframework.http.HttpStatus.FORBIDDEN
setup:
def openaiRequestCaptor = restExpectation.openai.completions(openaiResponse)
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 0
where:
openaiResponse | _
withResourceAccessException() | _
withStatus(FORBIDDEN) | _
对维雷莫克来说,一切都是一样的,除了反应形成略有不同( 测试代码 , 响应工厂类别代码 ).
使用@语言("JSON")注释以更好地实现IDD集成
在实现DSL时,有可能使用 @Language("JSON") 为智能j思想中的特定代码片段启用语言特性支持。例如,使用JSON,编辑器将将字符串参数作为JSON代码处理,使其具有语法突出显示、自动完成、错误检查、导航和结构搜索等功能。这里有一个注释的用法例子:
public static DefaultResponseCreator withSuccess(@Language("JSON") String body) {
return MockRestResponseCreators.withSuccess(body, APPLICATION_JSON);
}
以下是编辑的描述:
使用JSON断言进行结果验证
…JSONAssert 库的设计是为了简化JSON结构的测试。它使开发人员能够很容易地比较预期和实际的JSON字符串具有高度的灵活性,支持各种比较模式。
这样就可以从这样的验证描述中移动:
openaiRequestCaptor.body.model == "gpt-3.5-turbo"
openaiRequestCaptor.body.messages.size() == 1
openaiRequestCaptor.body.messages[0].role == "user"
openaiRequestCaptor.body.messages[0].content == "Hello!"
```
to something like this
```java
assertEquals("""{
"model": "gpt-3.5-turbo",
"messages": [{
"role": "user",
"content": "Hello!"
}]
}""", openaiRequestCaptor.bodyString, false)
在我看来,第二种方法的主要优点是,它确保了文档、日志和测试等各种上下文中数据表示的一致性。这大大简化了测试过程,提供了比较的灵活性和错误诊断的准确性。因此,我们不仅节省了编写和维护测试的时间,而且提高了测试的可读性和信息性。
当在弹簧引导中工作时,至少从版本2开始,不需要额外的依赖关系来使用库,因为 org.springframework.boot:spring-boot-starter-test 已经包括了 "天空尖叫者": .
在JSON文件中存储外部交互的规范
我们可以观察到,JSON字符串占用了测试的很大一部分。它们应该被隐藏吗?是和不是。了解什么能带来更多好处是很重要的。隐藏它们会使测试更加紧凑,并使初看之下的测试本质更加简单。另一方面,为了进行透彻的分析,有关外部交互规范的部分关键信息将被隐藏,需要在文件之间进行额外的跳跃。这个决定取决于是否方便:做对你来说更舒服的事。
如果选择将JSON字符串存储在文件中,一个简单的选择是将响应和请求分别保存在JSON文件中。以下是测试码( 全文 )展示实施方案:
爪哇河
1
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json")))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
… 文件格式 方法简单地从文件中读取字符串。src/test/resources 目录并没有任何革命性的想法,但仍然可以在项目存储库中供参考。
对于字符串的变量部分,建议使用替换 org.apache.commons.text.StringSubstitutor 在描述模拟时传递一组值。例如:
setup:
def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json",
[content: "Hello! How can I assist you today?"])))
JSON文件中替换的部分是这样的:
...
"message": {
"role": "assistant",
"content": "${content:-Hello there, how may I assist you today?}"
},
...
在采用文件存储方法时,开发人员面临的唯一挑战是在测试资源中开发适当的文件放置方案和命名方案。很容易犯错误,这会加重处理这些文件的经验。解决这一问题的一个办法是使用规格,例如来自PACT的规格,将在稍后讨论。
当在用LUovy编写的测试中使用所描述的方法时,您可能会遇到不便之处:对从代码中导航到文件的智能j思想没有支持,但是 预计今后将增加对这一功能的支持 .在java中编写的测试中,这种方法非常有效。
使用契约合同文件
从术语开始。
合同测试是一种测试集成点的方法,在这里,每个应用程序都进行单独测试,以确认它发送或接收的消息符合"合同"中记录的相互理解。"这一方法确保系统不同部分之间的互动符合预期。
合同测试中的合同是记录应用程序之间交换的消息(请求和响应)的格式和结构协议的文档或规范。它可以作为验证每个应用程序能够正确处理集成中其他应用程序发送和接收的数据的基础。
在消费者(例如希望检索某些数据的客户端)和提供者(例如提供客户端所需数据的服务器上的API)之间建立了合同。
消费者驱动测试是一种合同测试的方法,消费者在自动测试运行期间生成合同。这些合同将传递给提供者,然后提供者运行他们的一套自动化测试。将合同文件中的每一项请求发送给供应商,并将收到的响应与合同文件中指定的预期响应进行比较。如果两个响应都匹配,这意味着消费者和服务提供者是兼容的。
最后,PACT是实现消费者驱动的合同测试理念的一种工具。它支持基于http集成和消息集成的测试,侧重于代码第一测试开发。
正如我前面提到的,我们可以使用PACT的合同规格和工具来完成我们的任务。实现可能是这样的( 完整测试代码 ):
setup:
def openaiRequestCaptor = restExpectation.openai.completions(fromContract("openai/SuccessfulCompletion-Hello.json"))
def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}"))
when:
...
then:
openaiRequestCaptor.times == 1
telegramRequestCaptor.times == 1
合同档案是 可供审查 .
使用契约文件的优点是,它们不仅包含请求和响应体,而且还包含外部交互规范的其他元素--请求路径、标题和http响应状态,允许基于这种契约充分描述模拟。
重要的是要注意到,在这种情况下,我们只限于合同测试,而不扩展到消费者驱动的测试。然而,有人可能希望进一步探索《公约》。
结论
本文回顾了在与弹簧框架开发的背景下提高集成测试的可见性和效率的实际建议。我的目标是集中在明确定义外部交互的规范和最小化模板代码的重要性。为了实现这一目标,我建议使用DSL包装器、JSON断言、在JSON文件中存储规范,并通过PACT与合同一起工作。本文描述的方法旨在简化编写和维护测试的过程,提高测试可读性,最重要的是,通过准确反映系统组件之间的交互,提高测试本身的质量。