读书笔记《cloud-native-applications-in-java》测试云本地应用程序
在本章中,我们将深入测试云原生应用程序。使用各种测试工具、策略和模式,测试从手动测试到自动化测试已经成熟了很多。这种方法的好处是可以以对云开发很重要的故障保护方式频繁地进行测试。
我们将在本章中介绍以下主题:
- Testing concepts, such as behavior-driven development (BDD) and test-driven development (TDD)
- Testing patterns, such as A/B testing and test doubles
- Testing tools, such as JUnit, Cucumber, JaCoCo, and Spring Test
- Types of testing, such as unit, integration, performance, and stress testing
- Applying the concepts of BDD and integration testing to the Product service that we developed in Chapter 2, Writing Your First Cloud-Native Application, and enhanced in Chapter 4, Extending Your Cloud-Native Application
在本书中,我们开始在第 2 章, 在 Spring Boot 中编写您的第一个云原生应用程序,让您对云开发感到兴奋。然而,真正的开发遵循不同风格的最佳实践。
项目从了解需求 并编写验证需求的测试用例开始。由于此时代码不存在,因此测试用例将失败。然后,编写通过测试用例的代码。这个过程迭代直到测试用例和所需的代码完成以实现业务功能。 Kent Beck 有一本关于这个主题的优秀书籍 Test Driven Development by Example。在下一节中,我们将重做 Chapter 4 中的产品服务, 使用本章中的原则扩展您的云原生应用程序。但在此之前,让我们看看另一个重要的概念,BDD。
为云测试大型互联网应用程序需要一种规范的方法,其中一些模式可以派上用场。
A/B 测试(也称为 拆分测试)的初衷是实验 找出几个选定用户对 same 的两个不同网页的用户响应 功能。如果与另一组相比,用户对某个模式的反应良好,则选择该模式。
这个概念可以扩展到分阶段引入新功能。将功能、活动、布局或新服务介绍给一组受控用户并测量响应:
测试窗口结束后,将汇总结果以计划更新功能的有效性。
这种测试的策略是,对于选定的一组用户,使用 HTTP 302
(临时重定向)将用户从常规网站切换到新设计的网站。这将需要在测试期间运行网站或功能服务的变体。一旦测试成功,该功能会慢慢扩展到更多用户,并合并到主网站/代码库中。
通常,被测功能依赖依赖于其他团队独立开发的组件和API,具有以下缺点:
- They may not be available for testing at the time of development of your functionality
- They may not be always available and be set up with the data required for testing various cases
- Using the actual components each time may be slower
因此,test double 的概念变得流行起来。测试替身(如电影中的特技替身)是替换实际组件并模仿其行为的组件/API。测试双重组件通常是轻量级且易于更改的组件,由构建功能的团队控制,与可能是依赖项或外部流程的真实组件不同。
下一种类型的测试替身是 Mock 对象,它记录 system 的行为方式,然后呈现记录以供验证.例如,一个 Mock 数据库组件可以检查是否没有为应该从缓存层而不是数据库响应的产品调用它。
这是 Mocks 周围生态系统的基本图表表示:
在云开发中,您将构建一个服务,该服务依赖于其他服务或主要依赖于访问服务的API。通常,其他服务将无法立即用于测试。但是你不能阻止你的发展。这就是模拟或添加虚拟服务是测试服务的有用模式的地方。
服务模拟模拟真实服务的所有合同和行为。一些示例,例如 WireMock.org 或 Mockable.io 帮助我们模拟 API 并测试主要情况、边缘情况和失败情况。
我们在本章后面讨论的各种类型的测试甚至在云计算流行之前就已经为人所知。使用 持续集成 (CI) 和持续开发(CD)使得自动化这些类型变得很重要的测试,以便在每次代码签入和构建发生时执行。
单元测试的目的是测试每个类或代码 component 并确保它执行<一个 id="id288371778" class="indexterm"> 符合预期。 JUnit 是用于单元测试的流行 Java 框架。
使用 Mock 对象模式和测试存根,可以隔离被测试服务的依赖组件,以便测试集中在被测系统,也就是服务上。
JUnit 是最流行的单元测试工具。
负载测试包括在一段时间内向被测系统推送大量并发请求并观察效果,如响应系统上的时间和错误率。如果添加更多服务实例使其能够处理 额外负载,则称该系统具有水平可扩展性。
JMeter 和 Gatling 是覆盖这个维度的流行工具。
让我们将我们学到的测试原则应用到我们目前正在构建的 Product 服务中。我们从用户的角度开始,因此从验收测试开始。
第一步是回忆我们产品服务的规范。在第 4 章中, 扩展您的云原生应用程序< /span>,我们在产品服务上构建了一些功能,允许我们获取、添加、修改和删除产品,并获取 product 给定产品类别的 ID。
让我们将其表示为 Cucumber 中的特征。
- The first step in Cucumber is to express the user story as features with scenarios, and
Given
-When
-Then
conditions:Given
: Sets the preconditions for the behaviorWhen
: Trigger that changes the state of the system, for example, making a request to the serviceThen
: How the service should respond
- These are translated to automated test cases using the
cucumber-spring
translation layer so that they can be executed.
让我们从一个简单的 getProduct
验收测试用例开始。我们将在 Gherkin 中编写一个简单的功能,如果产品 ID 存在则获取产品,如果找不到产品 ID,则返回错误。
让我们以真正的 BDD 风格实现以下功能。产品服务上的 get
API 返回产品详细信息,例如描述和给定产品 ID 的类别 ID。如果找不到产品,它也可能返回错误,例如,404。让我们在 Gherkin 特征文件中将这两种行为表示为两个独立的场景。
功能:getProduct
获取给定产品 ID 的产品详细信息。
场景一:产品ID有效且存在。将返回其所属的产品名称和类别:
Given
the product service is runningWhen
the get product service is called with existing product ID1
Then
we should get a response with HTTP status code200
And
return product details with, nameApples
and category1
场景 2:产品 ID 无效或不存在。应该返回一个错误:
Given
product service is runningWhen
the get product service is called with a non-existing product ID456
Then
return a 404 not found statusAnd
return the error messageNo product for ID 456
场景 1 是一个成功的场景,其中返回并验证了数据库中存在的产品 ID。
方案 2 检查数据库中不存在的 ID 的故障情况。
每个场景分为多个部分。对于快乐路径场景:
Given
sets a precondition. In our case, it is simple enough: that the product service should be running.When
changes the state of the system and, in our case, it is making the request to the service by giving a product ID.Then
andAnd
are the results that are expected on the system. In this case, we expect the service to return a200
success code and the valid description and category codes for the given product.
您可能已经注意到,这是我们的服务文档,业务和测试团队以及开发人员都可以理解。它与技术无关;也就是说,如果实现是通过 Spring Boot、Ruby 或 .NET 微服务完成的,它不会改变。
在下一节中,我们会将服务映射到我们开发的 Spring Boot 应用程序。
Spring Boot Test 扩展并简化了 Spring Framework 提供的Spring-test 模块。让我们看看编写 acceptance 测试的基本要素,然后我们可以在本章后面重新审视细节:
- Copy the project that we created in Chapter 4, Extending Your Cloud-Native Application with HSQLDB and Hazelcast, as a new project for this chapter.
- Include the dependency on Spring in the Maven POM file:
您可能已经注意到,scope
更改为 test
。这意味着我们定义的依赖项对于正常运行时不是必需的,仅用于编译和测试执行。
- Add two more dependencies to Maven. We are downloading the libraries for Cucumber and its Java translation, along with
spring-boot-starter-test
:
CucumberTest
class 是启动 Cucumber 测试的主类:
RunWith
告诉 JUnit 使用 Spring 的测试支持,然后使用 Cucumber。我们给出了 .feature
文件的路径,该文件包含前面讨论的 Gherkin 中的测试用例。
Productservice.feature
文件 是包含 Gherkin 语言场景的文本文件,如前所述。我们将在这里展示两个测试用例。该文件位于 src/test/resources
文件夹中。
CucumberTestSteps
类包含将 Gherkin 中的步骤翻译成等效的 Java 代码。每个步骤对应一个方法,这些方法是根据 Gherkin 文件中的场景构建来调用的。让我们讨论与一个用例相关的所有步骤:
@SpringBootTest
注解告诉 Spring Boot Framework 它是一个测试类。 RANDOM_PORT
表示测试服务在随机端口启动Tomcat进行测试。
我们注入一个自动连接的 restTemplate
,这将有助于访问 HTTP/REST 服务并接收将被测试的响应。
现在,注意带有注释的方法 @Given
、@When
和 @Then< /代码>。每个方法都使用正则表达式来提取变量(从特征文件中)并将其用于方法中的断言。我们通过执行以下操作系统地测试了这一点:
- Checking whether the service is running first by accessing the
/health
(as we did for Spring Boot Actuator in Chapter 2, Writing Your First Cloud-Native Application). - Calling the service with the product ID.
- Checking whether the return code is
200
and the description and category of the response match the expected result. - Running the tests.
- Right-clicking the
CucumberTest.java
file and selectingRun As
|JUnit Test
:
您将看到控制台启动并显示启动消息。最后,JUnit 将测试结果反映如下:
JaCoCo 是 EclEmma 团队开发的一个代码 coverage 库。 JaCoCo 在 JVM 中嵌入了一个代理,它扫描遍历的代码路径并创建报告。
该报告可以导入更广泛的 DevOps 代码质量工具,例如 SonarQube。 SonarQube 是一个平台,它通过众多插件帮助管理代码质量,并与 DevOps 流程很好地集成(我们将在后面的章节中看到)。它是开源的,但也有商业版本。它是一个平台,因为它具有多个组件,例如服务器(计算引擎服务器、Web 服务器和 Elasticsearch)、数据库和特定于语言的扫描仪。
- First, include the plugin that includes JaCoCo in the POM file:
- The pre-execution prepares the agent to be configured and added to the command line.
- The post-execution ensures that the reports get created in the output folders:
- Finally, the created command-line change has to be inserted into the
maven-surefire-plugin
as follows:
- Now, we are all set to run the coverage report. Right-click on the project and select
Run As
|Maven test
to test the program, as shown in the following screenshot:
- As the console gets filled with the Spring Boot initiation, you will find the following lines:
- This tells us that two scenarios were executed with
8 Steps
(as before). But in addition,coverage-reports
got generated and placed in thetarget
directory:
- In the
site
folder, click onindex.html
; you will see the coverage report as follows:
- On investigation of the
product
package, you can see that theProductService
is only24%
covered, as shown in the following screenshot:
- The reason for this is that we have covered only the
getProduct
API in the service. TheinsertProduct
andupdateProduct
have not been covered. This is showcased in the drill-down report in the following screenshot:
- On the
getProduct
method, the coverage is complete. This is because, in two scenarios, we have covered the happy path as well as the error condition: