使用 JUnit 和 Maven 轻松进行性能测试
本文介绍了如何使用 JUnit 和 Maven 进行性能测试,也称为负载/压力测试。
本文已分为以下几个高级部分
引言
本文强调了性能测试的必要性,以及如何使用简单的 JUnit 和 Maven 基于开源库轻松、准确地进行性能测试。我们在应用程序的负载和压力测试中通常面临哪些问题,以及如何克服这些问题,并将performance-testing
(性能测试)作为 CI 构建管道的一部分。
问题
有时,我们倾向于认为性能测试不属于开发过程的一部分。这可能是因为在正常的开发冲刺中没有为此创建故事。这意味着产品或服务 API 的重要方面没有得到关注。但这并不是重点,重点是我们为什么要认为它不应该成为常规开发周期的一部分?或者...
为什么我们要把它放在项目周期的最后?
此外,为了支持上述观点,进行性能测试不像进行单元测试 、功能/组件测试、端到端集成测试或消费者-契约测试那样有直接的方法。然后,要求开发人员或性能测试人员(有时是专业团队)从市场选择一个独立工具,并生成一些漂亮的性能测试报告,与业务或技术团队共享这些报告。
这意味着它是孤立进行的,有时是在开发冲刺完成之后或临近完成时进行的,接近生产发布日期。因此,错过了这项测试的重要性,没有为改进或修复产品中的潜在问题留出空间。这回答了第一个问题“为什么”。
理想情况下,将其作为常规开发周期的一部分
性能测试应成为 CI 构建的一部分。
并且要使其成为 CI 构建的一部分,它必须足够容易,以便开发人员或性能测试人员可以编写/更改/共享测试。
关键在于,我们如何获得开发人员容易理解的有用统计数据,或者如何修复负载/压力/容量测试中出现的问题,或者
我们如何从 CI 构建中获得持续反馈?
在传统方法中,花费大量时间来理解工具和使工具工作,因为有些工具不适合 IDE,换句话说,甚至不是基于 Maven/JUnit 的。
一些挑战,例如将性能测试工具指向技术栈中的任何地方,都不是一个容易或直接的任务,例如,指向 REST 端点、SOAP 端点、DB 服务器或 KAFKA 主题或 MQ 端点(如 ActiveMQ/WMQ)、SSL 连接、通过企业代理的连接等。
这使得隔离问题变得有些困难,即您的应用程序API 性能非常好,但只有下游系统存在问题。让我们解释一下这意味着什么。
例如
您刚刚使用独立工具测试了 GET
API 的性能,指向 URL,例如 /api/v1/id-checks/131001,并发现响应延迟比单次调用同一个 API 时要长。然后,您(作为开发人员)倾向于将其归咎于 DB 或 MQ 主题,例如,给出的理由是 Oracle DB 服务器在处理并行负载时速度很慢。但是,您如何提供证据来支持您的论点,即您的 API 并不慢?
现在,您希望有一个机制或工具来隔离这个问题,方法是将性能测试工具直接指向 DB 或 KAFKA 主题等,因为(作为开发人员)您知道——哪些 SQL 查询被发送到 DB 以获取结果或您知道可以从中直接获取数据的主题名称,绕过应用程序 API 处理层,这可以更有意义地证明您的观点并提供证据。
要实现这些步骤,实际上应该非常容易,因为您已经拥有现有的 JUnit tests
、integration-tests
等,它们每天都在做同样的事情(实际上,每个 CI 构建都在这样做)。但有时使用独立的或市场上的性能工具很难做到,因为
您还没有找到一种方法来重用现有测试来生成负载/压力。
并且/或者它不够灵活,无法将这些现有测试馈送到您选择的工具中。所以您失去了兴趣,或者在冲刺中没有足够的时间对其进行探测,然后,您跳过了这项测试的方面,将责任推卸给下游系统或工具。
潜在解决方案
理想情况下,您需要一个自定义的JUnit 负载运行器,它可以轻松地让您重用现有的 JUnit tests
(端到端集成测试、功能测试或组件测试,因为它们大多数在后台使用 JUnit 或 TestNG)来生成负载或压力(在此处阅读负载与压力的区别),也称为水平负载与垂直负载)。
理想情况下,负载运行器应该如下所示,可以解决问题
@LoadWith("your_load_config.properties")
@TestMapping(testClass = YourExistingEndPointTest.class, testMethod = "aTest")
@RunWith(YourLoadRunner.class)
public class LoadTest {
}
其中 your_load_config.properties 应包含以下属性
number.of.parallel.users=50
users.to.be.ramped.up.in.seconds=50
repeat.this.loop.times=2
其中您希望在50 秒内将50 个用户加载起来(每个用户以1 秒的间隔触发测试),并且此操作应运行两次(loop=2),即总共100 个并行用户将触发请求,每个请求大约以1 秒的间隔。
而 @TestMapping
意味着
@TestMapping(testClass = YourExistingEndPointTest.class, testMethod = "aTest")
您上面的 YourExistingEndPointTest
的 aTest
方法应具有断言,以将实际接收到的结果与预期结果进行匹配。
负载运行完成后,您可能需要一个如下所示的精确统计数据
触发的总测试次数 | 100 |
通过的总测试次数 | 90 |
失败的总测试次数 | 10 |
请求之间的平均延迟(秒) | 1 |
平均响应时间延迟(秒) | 5 |
并且理想情况下,可以按需绘制更多统计数据,前提是您的 YourLoadRunner
可以生成CSV/电子表格,其中包含以下类型的数据(或更多)
测试类名 | TestMethod | 唯一测试 ID | 请求时间戳 | 响应延迟 | 响应时间戳 | 结果 |
您的现有测试 | aTest | test-id-001 | 2018-06-09T21:31:38.695 | 165 | 2018-06-09T21:31:38.860 | 通过 |
您的现有测试 | aTest | test-id-002 | 2018-06-09T21:31:39.695 | 169 | 2018-06-09T21:31:39.864 | 失败 |
当然,这些都是基本内容,您应该可以使用任何选定的工具来完成所有这些测试。
但是,如果您想为每个用户并发地触发不同类型的请求怎么办?
那么关于
- 一个用户想要触发POST,然后是 GET?
- 另一个用户继续触发POST > 然后 GET > 然后 PUT > 然后 GET 来验证所有CRUD 操作是否正常?
- 另一个用户每次触发请求时动态更改有效负载?
- 等等…每个场景都断言每个测试结果?
- 如果您想逐渐增加或减少被测应用程序的负载怎么办?
- 一个用户产生压力,另一个用户产生负载,两者同时进行?
现在您肯定需要一个机制来重用现有测试,因为您可能已经在常规端到端测试中测试了这些场景(顺序执行,但不是并行执行)。
所以您可能需要一个如下所示的 JUnit 运行器,它可以减轻重复做同样事情的痛苦。
使用开源库(JUnit 和 Maven 库)
最近,在进行性能测试时,我的团队偶然发现了这个名为 ZeroCode
的开源 Maven 库(请参阅 GitHub 上的 README),它提供了方便的 JUnit负载运行器,用于负载测试,使性能测试成为一项轻松的工作。我们富有创意地提出了许多场景,并成功地对我们的应用程序进行了负载测试。当然,我们同时将这些测试添加到了准备进行 CI 构建的负载回归包中。这有多棒!
这意味着我们能够简单地映射现有的 JUnit 测试到负载运行器。
基本上,我们将这两个库,即JUnit 和 Zerocode 结合起来生成负载/压力
查看基本示例
用法示例?
- 如需示例性能测试,请浏览 Maven performance-test 项目
- 下载(zip)并运行性能测试,解压缩并像普通 Maven 项目一样导入
- 浏览 helloworld 项目,了解 TDD/BDD 自动化的简单方法
关注点(报告、日志等)
当测试运行统计数据生成为 CSV 文件时,您可以使用这些数据集绘制任何图表。
这个库特别生成两种报告(请参阅此处示例负载测试报告)
- CSV 报告(位于 target/zerocode-junit-granular-report.csv)
- 交互式模糊搜索和过滤 HTML 报告(位于 target/zerocode-junit-interactive-fuzzy-search.html)
最重要的是,有时测试会失败,我们需要知道特定测试用例实例失败的原因。
如何跟踪失败的测试,包括请求/响应/请求时间/响应时间/失败原因?
您可以通过多种参数来跟踪失败的测试,但最简单的方法是使用其唯一步骤 ID。如何操作?
在 CSV 报告(以及 HTML 报告)中,您会找到一个名为 correlationId
的列,其中包含每次运行时测试步骤的唯一 ID。只需获取此 ID,然后在 target/zerocode_rest_bdd_logs.log 文件中搜索,您将获得与 TEST-STEP-CORRELATION-ID
匹配的完整详细信息,如下所示。
例如
2018-06-13 21:55:39,865 [main] INFO
org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl -
--------- TEST-STEP-CORRELATION-ID: a0ce510c-1cfb-4fc5-81dd-17901c7e2f6d ---------
*requestTimeStamp:2018-06-13T21:55:39.071
step:get_user_details
url:https://api.github.com:443/users/octocat
method:GET
request:
{ }
--------- TEST-STEP-CORRELATION-ID: a0ce510c-1cfb-4fc5-81dd-17901c7e2f6d ---------
Response:
{
"status" : 200,
"headers" : {
"Date" : [ [ "Wed, 13 Jun 2018 20:55:39 GMT" ] ],
"Server" : [ [ "GitHub.com" ] ],
"Transfer-Encoding" : [ [ "chunked" ] ],
"X-RateLimit-Limit" : [ [ "60" ] ],
"Vary" : [ [ "Accept" ] ]
"Status" : [ [ "200 OK" ] ]
},
"body" : {
"login" : "octocat",
"id" : 583231,
"updated_at" : "2018-05-23T04:11:38Z"
}
}
*responseTimeStamp:2018-06-13T21:55:39.849
*Response delay:778.0 milli-secs
---------> Assertion: <----------
{
"status" : 200,
"body" : {
"login" : "octocat-CRAZY",
"id" : 583231,
"type" : "User"
}
}
-done-
java.lang.RuntimeException: Assertion failed for :-
[GIVEN-the GitHub REST end point, WHEN-I invoke GET, THEN-I will receive the 200 status with body]
|
|
+---Step --> [get_user_details]
Failures:
---------
Assertion path '$.body.login' with actual value 'octocat'
did not match the expected value 'octocat-CRAZY'
并且可以绘制如下样本的吞吐量结果
当然,您可以使用Microsoft Excel 或任何其他方便的工具绘制折线图、饼图、3D 图表等(还有更多功能取决于您的业务需求)。
历史
以前,该库只提供一个负载运行器,但可以保留结构不变地操纵有效负载。但从最近的版本(v1.2.2 及更高版本)开始,可以加载多个并行用户,每个用户都可以动态更改有效负载。这有助于模拟生产环境的场景。