网站负载测试
本文档解释了关于网站负载测试的一些关键概念,包括
- 后端性能测试与前端性能测试之间的区别
- 何时选择基于协议、基于浏览器或混合脚本
- 关于如何负载测试网站的推荐实践
所有负载测试都试图模拟真实用户流量,以防止故障、提高可靠性并自信地发布新代码。但您的负载测试方法必须适应您想要测试的应用类型。
在本指南中,了解测试网站的策略,包括复杂场景脚本编写和测试执行的具体建议。
负载测试方法
在进行负载测试时,首先考虑以下几个角度
- 后端性能 vs. 前端性能
- 基于协议、基于浏览器或混合负载测试
- 组件测试 vs. 端到端测试
后端性能 vs. 前端性能
性能对网站的用户体验有重要影响。
例如,当用户想要可视化或与某些信息交互时,他们期望网站能够快速响应。
为了衡量性能,测试人员通常会关注响应时间,这受两个主要因素影响:
- 前端性能
- 后端性能
前端性能
前端性能测试验证应用在界面层面的性能,衡量考虑页面元素如何以及何时出现在屏幕上的往返指标。它关注应用的最终用户体验,通常涉及浏览器。
前端性能测试擅长识别微观层面的问题,但无法揭示系统底层架构中的问题。
由于它主要衡量单个用户对系统的体验,前端性能测试通常更容易在小规模下运行。前端性能测试拥有与后端性能测试不同的指标。前端性能测试关注诸如以下方面:
- 应用的页面是否经过优化,以便在用户屏幕上快速渲染
- 用户与应用 UI 元素交互所需的时间。
进行此类性能测试的一些顾虑包括其对完全集成环境的依赖性以及扩展成本。只有在应用代码和基础设施已与用户界面集成后,才能进行前端性能测试。自动化前端测试的工具本身也更消耗资源,因此大规模运行时成本可能很高,不适合进行高负载测试。
后端性能
后端性能测试针对底层应用服务器,查看它们在生产类似条件下的行为。对于网站而言,虽然前端性能涉及页面中包含的资产如何渲染,但后端性能则侧重于这些资产如何被应用服务器处理、服务于用户以及被浏览器下载。
后端测试的范围比前端性能测试更广。API 测试可用于针对特定组件或集成组件,这意味着应用团队拥有更大的灵活性,并更有可能更早地发现性能问题。后端测试比前端性能测试所需的资源更少,因此更适合生成高负载。
您应该测试哪一个?
视情况而定!理想情况下,两者都应测试。
前端测试工具在客户端执行,范围有限:它们无法提供足够的信息来对用户界面以外的后端组件进行精细调整。
当针对应用的流量增加时,这种限制可能导致对整体应用性能产生虚假信心。前端响应时间部分或多或少保持不变,但后端响应时间部分会随着并发用户数量呈指数级增长
仅测试前端性能会忽略应用很大一部分,而这部分在高负载下更容易出现更多故障和性能瓶颈。
另一方面,仅测试后端性能则忽略了用户体验的“第一英里”和广度。后端测试涉及协议层面的消息传递,而不是像真实用户那样与页面元素交互。它验证的是应用的基础,而不是用户最终看到的最高层。
同时测试前端和后端性能能为您的应用带来最佳的整体性能和用户体验。忽略其中任何一个都会使您面临性能瓶颈,从而显著降低用户对您应用的满意度。
然而,如果您的测试范围较小,您可以选择专注于前端或后端,最终目标是构建一个包含两者的测试套件。
组件测试 vs. 端到端测试
当您测试 Web 应用时,您可能会想知道如何构建测试脚本。考虑以下两种方法。
组件测试
负载测试 Web 应用的一种方法是负载测试其组件。也许您知道由于以前的生产问题导致特定功能存在问题,或者您想针对业务关键组件以降低风险暴露。
在这些情况下,您编写的测试类型可能
- 基于协议
- 仅调用相关的 API 端点
- 睡眠/思考时间较短或没有
- 专注于最终对组件或服务进行压力测试或找到其中断点
以这种方式进行测试更灵活。通过协议测试,您可以指定要访问的端点或服务器,从而缩小测试目标。您可以测试特定功能,同时跳过在标准用户流程中按时间顺序排在前面的其他功能。您可以更精细地控制生成的流量类型。如果您正在重新创建特定的请求组合,您可以编写这些请求的脚本并以可重复的方式重现它们。
进行组件负载测试可能不总是要求您的脚本行为像最终用户。事实上,可能需要人为增加流量以更快地重现问题,或者减少流量以降低日志中的噪音。由于这种测试类型的性质,脚本不包含您在生产中期望看到的完整请求流程,因此真实性不是首要任务。
端到端测试
您还可以对 Web 应用进行端到端测试。端到端测试旨在复制真实用户行为并跟踪其在整个技术栈中的影响。进行端到端测试时,您可能需要
- 进行协议级、浏览器级或混合负载测试
- 复制典型用户流程中的操作
- 考虑整个工作流的性能以及每个组件处理请求所需的时间
- 编写脚本,以用户访问应用的方式访问应用,例如在浏览到主页后再找到网站的其他部分
这种负载测试的范围比组件测试更广,但在深度上较浅。通过端到端测试,您可以更好地了解应用的整体用户体验。然而,它也可能更复杂地进行故障排除,因为您有更多的组件需要监控,以及更多地方需要查找发现的问题。
端到端测试基于真实用户行为,因此端到端测试脚本的真实性通常很重要。
基于协议、基于浏览器或混合负载测试
是否测试前端、后端或两者兼有的决定,也会影响您应进行的负载测试类型以及您应编写的脚本类型。
基于协议的负载测试
基于协议的负载测试通过模拟用户操作底层的请求来验证应用的后端性能。对于网站而言,这通常涉及绕过应用的用户界面并直接发送到服务器或应用组件的 HTTP 请求。
例如,一个基于协议的负载测试脚本可能会从应用服务器请求网页上的所有资源,但这些资源仅仅是被下载。纯粹基于协议的脚本报告的响应时间不包括前端指标,例如图像在浏览器上渲染所需的时间。负载是通过模拟发送到应用服务器的许多请求来生成的。
虽然基于协议的负载测试似乎更适合组件测试,但您也可以使用协议级脚本进行端到端网站测试。
基于协议的测试脚本示例
以下是 k6 中基于协议的负载测试脚本示例,它获取主页以及嵌入页面中的资源。
import http from 'k6/http';
import { sleep, check } from 'k6';
export function Homepage() {
const params = {
'sec-ch-ua': '"Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-GB,en;q=0.9',
};
// 01. Go to the homepage
let responses = http.batch([
['GET', 'https://mywebsite.com/', params],
['GET', 'https://mywebsite.com/style.min.css', params],
['GET', 'https://website.com/header.png', params],
['GET', 'https://website.com/polyfill.min.js', params],
]);
check(responses, {
'Homepage loaded': (r) => JSON.stringify(r).includes('Welcome to my site'),
});
sleep(4);
// 02. View products
responses = http.batch([
['GET', 'https://mywebsite.com/products', params],
['GET', 'https://mywebsite.com/style.css', params],
['GET', 'https://website.com/product1.jpg', params],
['GET', 'https://website.com/product2.jpg', params],
['GET', 'https://website.com/displaylist.js', params],
]);
check(responses, {
'Products loaded': (r) => JSON.stringify(r).includes('Add to Cart'),
});
sleep(1);
}
录制浏览器流量有助于您在协议级别原型化测试网站。
基于浏览器的负载测试
基于浏览器的负载测试通过模拟真实用户使用浏览器访问您的网站来验证应用的前端性能。
例如,基于浏览器的负载测试脚本可能包含导航到页面、点击按钮和在表单上输入文本的指令。这些用户操作随后会触发协议层的底层请求,但在基于浏览器的测试中只编写用户操作的脚本。
与基于协议的负载测试不同,基于浏览器的负载测试脚本通过启动多个浏览器实例并以真实用户的方式与您的应用交互来生成负载。对于许多应用逻辑由客户端脚本执行的单页应用 (SPA) 而言,在浏览器级别进行测试也可能是唯一的选择。
通常,在浏览器级别编写脚本需要使用与协议级别测试不同的工具。然而,k6 现在有一个名为 k6 browser 的模块,允许创建基于浏览器的测试脚本以及基于协议的脚本。
基于浏览器的测试脚本示例
以下是 k6 中基于浏览器的负载测试脚本示例,它使用 browser 模块测试一个虚拟网站。脚本不是发出 HTTP 请求,而是查看主页,然后查找并点击指向产品页面的链接。
import { browser } from 'k6/browser';
export default async function () {
const page = await browser.newPage();
// 01. Go to the homepage
try {
await page.goto('https://mywebsite.com');
await page.waitForSelector('p[class="woocommerce-result-count"]"]');
await page.screenshot({ path: 'screenshots/01_homepage.png' });
await page.waitForTimeout(4000);
// 02. View products
const element = page.locator(
'a[class="woocommerce-LoopProduct-link woocommerce-loop-product__link"]'
);
await element.click();
await page.waitForSelector('button[name="add-to-cart"]');
await page.screenshot({ path: 'screenshots/02_view-product.png' });
await page.waitForTimeout(1000);
} finally {
await page.close();
}
}
编写浏览器级脚本的技巧
以下步骤可以帮助您开始编写浏览器级测试脚本。
编写用户操作的脚本,而不是请求。确定用户执行特定任务可能做什么,并在浏览器级别编写与元素的交互脚本。例如,编写用户点击哪些按钮的脚本。
识别唯一选择器。确定用户与哪些页面元素交互后,使用浏览器 DevTools 中的元素检查器来查找识别每个元素的唯一、静态且简单的方法。脚本需要选择器来找到正确的元素进行交互。
使用元素验证响应。每次操作后,使用 locator 在页面上搜索您期望找到的元素。这种验证有助于确保脚本已到达预期页面。
调试时为每次操作截屏。基于浏览器的测试的优势之一是能够截屏。在脚本模拟的每次用户交互后,使用 page.screenshot 保存脚本遇到的视觉图像,以便后续故障排除。
混合负载测试
混合负载测试是基于协议和基于浏览器负载测试的组合。虽然您可以使用两种工具或两个脚本来执行不同类型的负载测试(一个基于协议,一个基于浏览器),但理想情况是使用相同的脚本和工具执行这两种类型的测试。在不同工具之间聚合结果可能非常困难,甚至不一致。
混合负载测试的最佳实践是使用协议级测试生成大部分负载,然后运行少量浏览器级测试脚本。这种方法
- 减少所需的负载生成器数量,因为协议级测试生成相同负载所需的机器更少
- 在同一个测试执行中衡量后端和前端性能
- 在结束时提供单一的聚合输出源
- 降低脚本创建和维护的复杂性
脚本编写考量
在为网站编写测试脚本时,请考虑以下建议。
考虑影响脚本真实性的因素
录制您的用户旅程。使用浏览器录制器可以通过捕获网页上的所有嵌入资源来方便地创建初始测试脚本。查看会话录制指南,了解如何从用户会话自动生成您的负载测试。
关联数据。录制通常不会考虑每次请求时新生成的动态值。检查录制的请求,确定是否需要从先前的响应中提取值并在后续请求中使用参数。这种做法能确保您的虚拟用户行为更像真实用户。
包含或排除静态资源。确定是否应包含或排除页面上的静态资源,例如图像、JavaScript 等。如果您想衡量整体用户体验,请考虑包含它们。如果您正在使用一个独立的服务水平协议 (SLA) 下的内容分发网络 (CDN),请考虑排除它们。
排除第三方请求。不要对您不拥有的服务器进行负载测试。许多应用程序会调用第三方提供商进行身份验证、社交分享和营销分析。除非您有权限将这些请求包含在测试中,否则请禁用它们。
使用并发请求。为了模拟现代浏览器并行下载某些请求的方式,请使用批处理。
确定缓存和 Cookie 行为。k6 会在迭代之间自动重置 Cookie,但您也可以更改此行为,如果保持 Cookie 能使测试更真实的话。
使用动态思考时间和步调。考虑添加可变的延迟,以免使用完全一致的延迟人为地错开脚本。
使用测试数据。真实用户通常不会重复搜索或提交相同的数据。考虑添加一个测试数据文件供脚本迭代使用。
根据生产环境建模测试参数和负载特征。在 k6 中,您可以使用测试选项来确定负载测试脚本的确切形状和特征。为工作选择合适的执行器。
创建一个可重用的框架
使用标签和分组。通过对请求进行标记和分组来组织它们,这有助于您整合同类指标,并使您的测试脚本对其他人更易于理解。
使用场景。当结合基于协议和基于浏览器的测试时,使用场景来独立控制其测试参数和执行器。
模块化脚本。使用模块分离和组织协议级测试和浏览器级测试的函数,然后使用测试运行器脚本来执行它们。这种方法意味着不同的脚本可以进行版本控制和更改,而不会相互影响。
将您的测试集成到 CI 流水线中。采用“测试即代码”的方法可以使您的负载测试与项目现有的 CI/CD 流程更紧密地结合,帮助您从每次测试中获得最大价值。
考虑阈值进行测试
为两种类型的测试创建阈值。某些浏览器级和协议级指标不能合并,因为它们衡量的东西不同。为浏览器级脚本和协议级脚本中的相关指标设置阈值。
如果可能,使用混合负载测试
使用基于协议的脚本生成大部分负载。编写测试场景时,使用基于协议的请求来模拟大部分流量,并为基于浏览器的请求使用较少的虚拟用户。依赖协议级流量有助于降低负载生成器上的资源利用率。
执行注意事项
运行测试时,请考虑测试环境和负载生成器位置。
在适当的环境中运行测试
在预生产环境和生产环境中进行测试都具有价值。
在预生产环境(暂存、测试、系统集成测试、用户验收测试和生产副本环境)中进行测试可以帮助您尽早发现性能缺陷,这可以在后续节省大量时间和精力(和声誉)。在测试环境中运行也意味着您通常可以更大胆地进行测试。然而,正确设置负载特征也更为关键,并且您获得的测试结果可能不一定适用于生产环境。
在生产环境中进行测试可以获得最准确的结果,但风险也更高。通常,在生产环境中进行测试是唯一可行的替代方案。您可以通过以下方式降低在生产环境中进行测试时对真实客户的影响风险:在高峰时段运行负载测试时使用较低的负载水平,安排在非高峰时段进行测试,选择风险较低的负载测试类型,使用像合成监控这样生成流量较少的技术,使用真实用户监控工具在负载下获取用户性能快照,并确保您的可观测性堆栈处于最佳效率状态。
在客户所在的地方运行测试
负载生成器的位置(流量来自哪里)也会影响您的测试结果。问题是:您的最终用户位于哪里?
本地负载测试在网站开发早期或有机器可用于负载生成器时可能是理想的选择。然而,完全在公司内部网络进行测试也可能产生误报,因为报告的响应时间远低于从全国各地访问相同应用服务器的响应时间。
云上负载测试是许多面向公众网站测试策略的重要组成部分。在云上使用负载生成器使您能够在不同州和地理国家进行测试,创建与用户位置成比例的负载生成器组合。云负载生成器比本地负载生成器更容易配置且长期维护成本更低。在云上进行负载测试可以帮助您在测试中包含网络延迟的影响,并获得更真实的结果。
建议
以下是一些建议,帮助您规划、编写脚本和执行网站的负载测试。
如果您想测试您网站的最后一英里用户体验
- 侧重于前端性能,
- 编写基于浏览器的测试脚本,
- 并考虑进行更真实的端到端用户流程测试。
如果您想测试您网站的基础设施
- 侧重于后端性能,
- 编写基于协议的测试脚本,
- 并考虑从组件测试开始,然后逐步增加范围。
如果您的网站用于内部或访问受限
- 使用位于大多数用户访问网站的网络内的本地负载生成器。
如果您的网站是外部且面向公众
- 在您的用户所在的负载区域使用云负载生成器。
尽可能在预生产环境中进行测试,但也要考虑在生产环境中进行有限容量的测试。
由于可行的测试方法、性能测试范围以及发布未充分测试代码的潜在影响,网站负载测试可能很复杂。通过遵循我们在此提出的建议,您可以更贴合您的目标来定制您的测试。