读书笔记《building-a-restful-web-service-with-spring》测试REST风格的Web服务
由于我们将 RESTful 端点声明为带有注释的Java 方法,因此可以使用正常的单元测试技术。 Java 的实际单元测试 库是 JUnit。 (http://www.junit.org)。 JUnit 是一个用于编写可重复测试的简单框架。以下代码片段说明了如何测试 RESTful 端点:
大多数读者都会熟悉这些类型的测试。在这里,我们验证了示例物业管理系统的可用性组件 不接受无效日期。此代码片段中未涵盖的是 com.packtpub.springrest.availability.AvailabilityService
和 org.springframework.web 的实例。获取到 context.request.WebRequest
。这两个接口是依赖关系,我们并不想使用真正的实现。我们只对测试资源类中的代码感兴趣。下一节将讨论处理这种情况的模拟技术。
我们通过使用 Java 接口将 RESTful 层与服务实现分离,构建了我们的示例 属性管理系统。除了帮助构建代码库和防止紧密耦合之外,此过程还有益于单元测试。实际上,在测试应用程序的 Web 层时,我们可以使用模拟的实现,而不是使用具体的服务实现。例如,考虑以下单元测试:
当为现有房间调用 RoomResource.getRoom()
时,我们期望包含表示该房间的有效负载的非空成功响应。我们可以创建一个模拟实现,而不是使用依赖于数据库的真正的 com.packtpub.springrest.inventory.InventoryService
实现。这个模拟必须表现出预定义的行为,允许我们测试由 com.packtpub.springrest.inventory.web.RoomsResource
处理的不同用例。这可以通过两种不同的方式实现,如 将在下一节中说明。
为了提供 必要的行为,我们可以简单地创建 Inventory Service 的匿名实现:
使用这种方法,我们手动实现 InventoryService
接口并插入我们需要的代码。此外,我们为 roomId
值返回 Room
值,1
,否则抛出 RecordNotFoundException
。
为了便于阅读,我们省略了此代码片段中公开的所有其他方法的存根。但这就是挑战在于简单模拟的地方。开发人员必须编写大量代码,这些代码对单元测试没有任何价值,但每当要向服务添加新功能时都需要维护。这就是模拟库 发挥作用的地方。下一节将讨论这样一个库的使用。
在 Java 中模拟库时,有几个 选项,例如 jMock (http://www.jmock.org) 或 EasyMock (http://easymock.org)。最受欢迎的一种是 Mockito。 Mockito 是一个模拟框架,可让开发人员编写干净的测试并提供 简单的 API。开发人员可以通过向项目描述符添加 以下依赖项来将 Mockito 添加到他们的 Maven 项目中:
为了说明如何使用 Mockito,请看以下测试:
该测试验证可以更新现有房间。我们不是自己实现 InventoryService
,而是使用 Mockito 创建一个带有 mock(InventoryService.class)
的模拟实现。下一步是指示我们的模拟在调用时返回房间和房间类别。这是通过 when(...).thenReturn(...)
实现的。这种模式为我们的模拟添加了行为。例如, when(service.getRoom(1)).thenReturn(room)
将使我们的模拟服务返回一个房间 当为房间 ID 1
调用 InventoryService.getRoom()
时。
Spring 提供 一种机制,使用 @org.springframework.beans.factory.annotation 自动将 bean 注入到类中。自动接线
。此注解可用作 如下:
在启动时,Spring 将查找类型为 InventoryService
的任何已声明 bean,并将其注入。这种机制减少了需要实现的样板代码量。但是,它使测试变得有点棘手,因为对于这段代码,我们无法直接访问 bookingService
实例变量。值得庆幸的是,Mockito 提供了几个有用的注释来规避这个限制。我们可以如下声明我们的测试类:
第一个注释 (@org.junit.runner.RunWith
) 允许我们用标准的 JUnit runner 代替 Mockito 的。使用 @org.mockito.InjectMocks
,我们标记要注入的字段。此外,我们用 @org.mockito.Mock
注释 bookingService
。这将自动生成一个模拟,该模拟将被注入到我们的 BookingsResource
实例中。然后我们可以使用为我们实例化的资源和服务类来运行我们的测试。
Note
在他们的 网站上阅读更多关于 Mockito 的信息:http ://mockito.org。
Spring 对单元测试有很好的支持。开发人员可以向他们的 Maven 项目添加新的依赖项以包括 Spring 测试支持,如下所示:
因为我们不希望在发布的产品中包含这些依赖项,所以我们指定 test
作为范围。这样做只会使库在测试期间在类路径上可用。
在第7章中,处理安全,我们为我们的示例物业管理系统的预订组件增加了安全性。作为参考,我们限制了对 RESTful 端点的访问以检索预订,如下所示:
使用标准单元测试,我们无法验证只有管理员才能访问此端点。使用 Spring 的测试支持,我们可以为这个端点编写单元测试,如下所示:
通过 用 @org.junit.runner.RunWith
注释测试类,我们替换了标准的 JUnit runner与春天的。
因为在 testGetBookingNotLoggedIn()
中没有定义身份验证,所以我们预计会抛出异常。
感谢@org.springframework.security.test.context.support.WithMockUser
,我们的第二个测试方法声明了一个用户。但是,由于用户具有角色 USER
,默认情况下,我们也期望抛出异常。
最后,第三个测试方法 (testGetBookingValidUser()
) 声明了一个具有正确角色的用户,因此可以继续检索 ID 为 1 的预订
。
单元测试有助于确保各个类表现出正确的行为;集成测试侧重于系统不同组件之间的交互。系统越大,这种形式的测试就越重要。以下两节提供了创建有效集成策略的技术。
持续交付是一种软件工程实践,提倡在短周期内生产可以可靠发布的软件。传统上,一旦将更改提交到代码库,服务器就会构建软件并运行完整的测试套件。如果成功,软件可以自动部署到登台环境。然后可以针对新版本的软件执行集成测试。
使用 Maven,我们可以定义一种简单的方法,通过它们的命名约定将单元测试与集成测试分开;集成测试应该有后缀,IntegrationTest
。然后可以将以下内容添加到项目描述符中:
使用此配置,可以首先运行单元测试。将软件部署到登台环境后,就可以运行集成测试。
如以下代码片段:
如本章前面所述,@org.junit.runner.RunWith
注解允许使用替代测试运行器。在这种情况下,我们将 JUnit 的默认运行器替换为 Spring 的。另外,我们通过@org.springframework.boot.test.IntegrationTest
指定要连接的地址。使用这些注释,我们可以连接到运行我们的示例预订服务的服务器,并针对它运行一套集成测试。
正如我们在第5章中简要讨论的,REST 中的 CRUD 操作,我们可以利用 Postman (https://www.getpostman.com) 编写测试套件以验证我们的 RESTful Web 服务。这种类型的测试实际上是集成测试。例如,如果我们编写一个通过 REST API 检查 房间可用性的测试,我们将测试系统的可用性、库存和预订组件。 Postman 提供了定义测试集合的能力,如以下屏幕截图所示:
我们可以快速运行对暂存环境的整个调用集合,并确保返回预期的响应。
对于安全调用,例如 Chapter 7 中描述的调用,处理安全性< /em>,我们需要指示 Postman 发送正确的标头。对于 HTTP Basic 身份验证,我们首先需要生成 Base64 编码的用户名和密码对(例如 cmVzdDpyb2Nrcw==
for rest:rocks< /code>),并添加
Authorization
标头以保护调用:
添加此标头后,调用将在服务器上进行身份验证并能够继续。
除了 unit 和集成测试之外,还应考虑其他形式的测试。它们将在下一节中描述。
用户验收测试(UAT)着眼于用户的测试观点看法。对于 API,用户是使用服务的软件。无论用户类型如何,这种测试形式对于确保 RESTful Web 服务公开一致且功能完整的 API 都很重要。 UAT 的自动化程度往往低于其他类型的测试。但是,UAT 测试经理最终应该对软件解决方案是否已准备好通用 可用性拥有最终决定权。
衡量 RESTful Web 服务的生产准备情况的另一个重要标准是将按照预期的服务水平协议(SLA ) 在负载下。例如,在高峰时段,该服务可能预计每秒处理 1,000 个请求,平均响应时间不超过 250 毫秒。
有许多商业 和开源产品来测试Web 服务是否可以处理这种负载。最常见的是开源软件 Apache JMeter (http://jmeter.apache.org)。使用 JMeter,开发人员可以创建可以以定义的速率执行并捕获响应时间的测试计划。下面的屏幕截图显示了运行测试计划的结果,该计划包含对我们的示例物业管理系统的一次调用,以检索 ID 为 1
的房间:
我们同时执行了 http://localhost:8080/rooms/1
1000 次(10 个线程),平均响应时间为 11 毫秒。通过增加线程数,我们可以模拟更多的服务负载。