从零掌握Locust:Python分布式压测工具实战指南

📅 2026/7/7 19:52:14
从零掌握Locust:Python分布式压测工具实战指南
1. 项目概述为什么选择Locust作为压测工具在开发和运维的日常工作中性能测试是一个绕不开的话题。无论是上线一个新功能还是评估一次架构升级我们都需要一个可靠的“压力源”来模拟真实用户看看系统在高并发下的表现到底如何。市面上压测工具不少从老牌的JMeter、LoadRunner到新兴的k6、Gatling各有千秋。但今天我想聊聊Locust一个用Python写的开源分布式性能测试工具。我选择它不是因为它在所有方面都最强而是因为它在一个特定场景下——对于开发者和测试人员来说——特别“趁手”。Locust的核心魅力在于“代码即脚本”。它允许你用纯Python代码来定义用户行为这带来了极大的灵活性。你不再需要在一个复杂的GUI里拖拽元件、配置参数而是像写业务逻辑一样用requests库或者任何你熟悉的HTTP客户端库去描述一个用户的完整操作流。这对于习惯用代码解决问题的团队来说学习成本极低维护和版本控制也异常方便。另一个关键点是它的分布式架构设计得很轻巧一个主节点Master可以协调成百上千个从节点Worker同时发压资源利用率高扩展性强。结合它自带的Web UI你可以实时看到RPS每秒请求数、响应时间、失败率等关键指标测试过程直观可控。所以如果你或你的团队符合以下情况Locust很可能就是你的菜团队技术栈以Python为主希望压测脚本能像普通代码一样被Git管理、被CI/CD集成测试场景需要复杂的逻辑比如依赖前序请求的结果、有条件分支、需要处理动态数据或者你只是单纯厌倦了笨重的图形化工具想要一个更“极客”、更轻量的解决方案。接下来我会从零开始带你完成Locust的安装、脚本编写、执行到结果分析的完整闭环并分享一些我踩过坑才总结出来的实战经验。2. 环境准备与Locust安装详解工欲善其事必先利其器。安装Locust本身很简单但一个稳定、隔离的Python环境是高效工作的前提。我强烈建议你不要在系统的全局Python环境里直接安装而是使用虚拟环境。这能避免不同项目间的依赖冲突也让环境清理变得轻而易举。2.1 创建并激活Python虚拟环境无论你使用Windows、macOS还是Linux虚拟环境都是第一步。这里以macOS/Linux和Windows的常见操作为例。对于macOS或Linux用户打开终端。首先确保你安装了python3和pip3。通常系统会自带如果没有可以通过系统包管理器如apt、yum或brew安装。然后我们使用Python内置的venv模块来创建虚拟环境。# 创建一个名为 locust_env 的虚拟环境目录 python3 -m venv locust_env # 激活虚拟环境 # 在macOS/Linux上 source locust_env/bin/activate激活后你的命令行提示符前通常会显示(locust_env)表示你已经进入了这个隔离的环境。对于Windows用户操作在命令提示符CMD或PowerShell中进行。# 创建虚拟环境 python -m venv locust_env # 激活虚拟环境 # 在CMD中 locust_env\Scripts\activate.bat # 在PowerShell中 locust_env\Scripts\Activate.ps1如果PowerShell执行策略阻止了脚本运行你可能需要先以管理员身份运行Set-ExecutionPolicy RemoteSigned来更改策略操作后请记得改回安全第一。注意虚拟环境激活后所有通过pip安装的包都只会存在于这个环境中不会影响系统其他部分。工作完成后输入deactivate命令即可退出虚拟环境。2.2 安装Locust及其依赖环境激活后安装Locust就一行命令的事。但这里有个小技巧直接使用pip安装时可以指定版本也可以选择安装一些额外的依赖包以支持更多功能。# 安装最新稳定版的Locust pip install locust # 如果你想安装一个特定版本例如2.20.0 # pip install locust2.20.0 # 安装完成后验证安装是否成功 locust --version如果安装成功命令行会输出Locust的版本号例如locust 2.20.0。有时基础的Locust包可能不包含所有格式的报表输出依赖。如果你需要生成HTML报告可以安装locust-plugins这个社区维护的插件包它提供了更多好用的功能比如额外的监听器、表单提交器等。pip install locust-plugins2.3 可能遇到的安装问题与解决安装过程通常很顺利但偶尔也会遇到一些小麻烦。这里记录几个我遇到过的高频问题。问题一pip安装速度慢或超时。这是因为默认的PyPI源服务器在国外。解决方法是指定国内的镜像源来加速下载。# 使用清华源安装 pip install locust -i https://pypi.tuna.tsinghua.edu.cn/simple常用的国内镜像源还有阿里云(https://mirrors.aliyun.com/pypi/simple/)、豆瓣(https://pypi.douban.com/simple/)等。问题二提示缺少gevent或greenlet等底层依赖。Locust基于gevent这个高性能的并发库。在某些系统上特别是Windows编译这些C扩展可能会失败。最直接的解决办法是安装预编译的二进制轮子wheel。# 首先尝试升级pip和setuptools pip install --upgrade pip setuptools wheel # 然后再次安装locustpip会自动寻找适合你平台的wheel包 pip install locust如果还是不行可以去 这个网站 手动下载对应Python版本和系统位数的gevent和greenlet的.whl文件然后用pip install 文件名.whl的方式本地安装之后再安装Locust。问题三安装后locust命令找不到。这通常发生在Windows上且没有将Python的Scripts目录添加到系统环境变量PATH中。有两种解决方法一是找到你的虚拟环境目录下的Scripts文件夹例如C:\Users\YourName\locust_env\Scripts然后在这个文件夹里打开命令行执行locust命令二是在系统环境变量PATH中永久添加这个路径。对于临时测试第一种方法更安全。3. 编写你的第一个Locust压测脚本安装完毕我们来动手写脚本。Locust脚本的本质是一个Python模块其中必须定义一个继承自HttpUser或更基础的User的用户类。在这个类里你可以定义用户的“任务集”和“等待时间”。我们从一个最简单的例子开始模拟用户访问一个网页的首页和“关于我们”页面。3.1 脚本基础结构与核心概念创建一个名为locustfile.py的文件这是Locust默认寻找的入口文件名并输入以下内容from locust import HttpUser, task, between class QuickstartUser(HttpUser): # between(min, max) 定义用户在执行任务后等待min到max秒之间的一个随机时间 wait_time between(1, 5) # 使用task装饰器来标记这是一个用户任务权重值默认为1 task def index_page(self): # self.client是HttpUser内置的类requests的客户端支持所有常用HTTP方法 self.client.get(/) task(3) # 权重为3意味着执行频率是index_page任务的3倍 def about_page(self): self.client.get(/about)我们来拆解一下这个脚本HttpUser: 这是一个用于模拟HTTP用户的类。每个模拟用户蝗虫在运行时都会是这个类的实例。wait_time: 定义用户思考时间。between(1, 5)表示每次任务执行完后会等待1到5秒之间的一个随机时间再执行下一个任务。这模拟了真实用户的操作间隔。你还可以使用constant(1)表示固定等待1秒或者用constant_pacing(1)来确保任务执行间隔至少为1秒如果任务执行时间超过1秒则无等待。task: 这是最重要的装饰器。它把下面的方法标记为一个用户任务。括号里的数字是权重权重越高被选中的概率越大。在上面的例子中about_page被调用的概率是index_page的3倍。self.client: 这是HttpUser提供的HttpSession实例其API与流行的requests库高度一致所以你会感到非常熟悉。它自动处理cookies并在请求间保持会话。3.2 实现复杂的用户行为逻辑真实的用户行为很少是简单的、独立的页面访问。更多时候我们需要模拟一个完整的业务流程比如登录 - 浏览商品列表 - 查看商品详情 - 加入购物车 - 下单。这就要求我们的脚本能够处理请求间的依赖例如从登录响应中提取token或者从商品列表页中随机选择一个商品ID。下面是一个模拟“登录后查询信息”的增强版脚本示例from locust import HttpUser, task, between import random class ApiUser(HttpUser): wait_time between(0.5, 2.5) host https://api.example.com # 设置基础URL后续请求会基于此 def on_start(self): 每个用户实例启动时自动执行一次常用于登录等初始化操作 login_response self.client.post(/auth/login, json{ username: test_user, password: test_pass }) if login_response.status_code 200: # 假设登录成功后返回一个token self.token login_response.json().get(access_token) # 为后续请求设置认证头 self.client.headers.update({Authorization: fBearer {self.token}}) else: # 如果登录失败这个用户实例后续任务将无法执行可以标记为失败或停止 self.environment.runner.quit() print(Login failed for a user) task(5) def get_profile(self): # 使用已携带认证头的client发起请求 with self.client.get(/user/profile, catch_responseTrue) as response: # catch_responseTrue 允许我们手动控制请求的成功/失败判定 if response.status_code 200: response.success() else: response.failure(fGot status code: {response.status_code}) task(2) def search_items(self): # 模拟带查询参数的请求 keywords [book, laptop, phone] self.client.get(f/items/search?keyword{random.choice(keywords)}page1) task(1) def create_item(self): # 模拟一个POST请求创建资源 payload { name: fTest Item {random.randint(1000, 9999)}, price: round(random.uniform(10.0, 100.0), 2) } self.client.post(/items, jsonpayload, name/items [POST]) # 使用name参数可以覆盖Locust统计中的请求名称避免因动态参数产生大量独立条目关键点解析on_start方法每个虚拟用户在开始执行task任务前会先执行一次这个方法。这是放置登录、获取初始数据等前置操作的绝佳位置。catch_responseTrue这是一个非常重要的参数。默认情况下Locust将HTTP状态码为2xx的请求视为成功4xx/5xx视为失败。但有时业务逻辑的成功失败不能仅看状态码。例如一个返回200的请求其JSON体里可能有一个success: false的字段。使用catch_responseTrue后你需要手动调用response.success()或response.failure()来告诉Locust这个请求的最终结果。这能让你的测试结果更精确地反映业务状态。name参数在self.client的请求方法中指定name可以覆盖Locust统计中这个请求的命名。如果不指定Locust会使用完整的URL路径如/items/123作为名称。当路径中有动态部分如ID时会导致统计页面出现成千上万个独立的条目使得图表无法阅读。通过指定一个统一的name如/items [POST]可以将所有同类请求聚合在一起统计清晰得多。3.3 脚本组织与高级技巧当脚本越来越复杂时良好的代码组织至关重要。你可以将公共方法如认证、数据生成提取到基类中或者使用TaskSet来将任务分组。TaskSet允许你将一系列相关的任务打包成一个集合并在不同的用户类间复用。from locust import HttpUser, task, TaskSet, between class BrowseTasks(TaskSet): # 这个TaskSet内的任务权重是独立的 task(10) def view_index(self): self.client.get(/) task(5) def view_category(self): categories [electronics, clothing, books] self.client.get(f/category/{random.choice(categories)}, name/category/[id]) task(1) def stop(self): # 调用self.interrupt()可以中断当前TaskSet的执行返回到父级 self.interrupt() class PurchaseTasks(TaskSet): task def add_to_cart(self): item_id random.randint(1, 100) self.client.post(f/cart/add/{item_id}, name/cart/add/[id]) task def checkout(self): self.client.get(/checkout) class WebsiteUser(HttpUser): wait_time between(1, 3) # tasks是一个列表可以包含元组 (TaskSet类, 权重) 或普通任务函数 tasks [ (BrowseTasks, 3), # 有3/4的概率执行BrowseTasks (PurchaseTasks, 1), # 有1/4的概率执行PurchaseTasks ] # 也可以直接定义任务函数 task def about(self): self.client.get(/about)在这个例子中WebsiteUser有75%的概率去执行浏览任务集(BrowseTasks)25%的概率去执行购买任务集(PurchaseTasks)。在每个任务集内部又有各自权重的任务分布。这种结构让复杂用户行为的建模变得清晰且模块化。实操心得在编写复杂脚本时我习惯先画出用户操作流程图然后根据流程图来设计TaskSet和task的层级与权重。另外将数据生成如随机用户名、商品ID和工具函数如解析响应提取token单独放在一个utils.py模块中能让主脚本保持简洁。4. 执行压测与Web UI实战解析脚本写好之后就可以开始“放蝗虫”了。Locust的执行方式非常灵活既可以通过命令行快速启动也可以通过Web UI进行交互式控制这对于调试和演示特别友好。4.1 启动Locust与基础命令行参数最基础的启动命令就是在你的locustfile.py所在目录下运行locust默认情况下Locust会寻找当前目录下的locustfile.py作为入口文件并启动Web UI在http://localhost:8089。但通常我们需要指定更多参数。一个更典型的启动命令如下locust -f locustfile.py --hosthttps://your-target-server.com --headless -u 100 -r 10 -t 2m我们来分解这些参数-f locustfile.py: 指定入口脚本文件。如果你的脚本叫别的名字比如stress_test.py就需要用-f stress_test.py。--hosthttps://your-target-server.com: 这是最重要的参数之一。它设置了所有请求的基础URL。在脚本中self.client.get(/api)实际上会访问https://your-target-server.com/api。如果脚本里使用了绝对URL则会忽略这个参数。--headless: 无头模式。不启动Web UI直接开始测试。适用于CI/CD流水线或自动化测试场景。-u 100: 设置模拟的总用户数--users。在--headless模式下Locust会直接启动这么多用户并发压测。-r 10: 设置每秒启动的用户数--spawn-rate。这里表示每秒启动10个用户直到达到100个的总数。-t 2m: 设置测试运行的时间--run-time。支持s秒、m分、h时等单位。例如-t 1h30m。时间到后测试自动停止。如果不使用--headless模式直接运行locust则会启动Web界面。此时你需要在浏览器中打开http://localhost:8089手动输入用户数、增速和主机地址然后点击“Start swarming”开始测试。4.2 Web UI界面深度解读与操作Web UI是Locust的一大特色它提供了一个实时监控测试的仪表盘。理解每个图表和数字的含义对于分析性能瓶颈至关重要。启动Locust并打开Web UI后你会看到以下主要区域测试配置区在这里输入Number of users总用户数、Spawn rate每秒启动用户数和Host目标主机。点击“Start swarming”开始。实时统计仪表盘Statistics统计标签页这是核心。表格展示了每个请求类型由name参数定义的详细数据。Type: 请求名称。Requests: 总请求数。Fails: 失败请求数。Median, 90%, 95%, 99%: 响应时间的百分位数。例如“95%”表示95%的请求响应时间低于这个值。95%或99%响应时间P95/P99是评估系统稳定性的黄金指标它比平均响应时间更能反映尾部延迟即最慢的那部分请求的情况。Average: 平均响应时间。Min/Max: 最小/最大响应时间。Average size: 平均响应体大小。Current RPS: 当前每秒请求数。Current Failures/s: 当前每秒失败数。Charts图表标签页这里以曲线图形式展示了总RPS、响应时间按百分位和用户数随时间的变化。你可以非常直观地看到随着用户数增加系统性能的变化趋势。Failures失败标签页列出了所有失败的请求包括URL、异常信息和发生时间方便快速定位问题。Exceptions异常标签页显示了脚本运行过程中抛出的Python异常。Download Data下载数据标签页测试结束后可以在这里下载CSV格式的请求统计数据和失败记录用于后续更细致的分析或报告生成。操作技巧动态调整负载在测试运行过程中你可以在右上角实时修改用户数和增速然后点击“Update”按钮。Locust会平滑地增加或减少用户而无需停止测试。这对于做“阶梯式增压”测试非常有用。停止与重置点击“Stop”停止发压但UI数据会保留。点击“New test”可以完全重置所有统计数据开始一轮全新的测试。标签页筛选在“Statistics”页你可以通过顶部的输入框筛选特定的请求类型这在接口很多时非常方便。4.3 分布式执行模式搭建当需要模拟非常高的并发用户数例如上万时单台机器可能成为瓶颈受限于CPU、网络或端口数。Locust原生支持分布式运行。架构很简单一个Master节点和多个Worker节点。Master负责协调、分发任务和收集汇总数据Worker节点负责实际执行任务、生成负载。搭建步骤启动Master节点在一台机器上使用--master参数启动。locust -f locustfile.py --master --hosthttps://your-target-server.comMaster节点默认会启动Web UI端口8089但自身不产生任何负载。启动Worker节点在另外一台或多台机器上也可以在同一台机器的不同端口使用--worker和--master-host参数启动。# 假设Master节点的IP是192.168.1.100 locust -f locustfile.py --worker --master-host192.168.1.100Worker节点需要能访问到Master节点通过--master-host指定并且需要拥有完全相同的locustfile.py脚本和Python环境。在Web UI中控制打开Master节点的Web UIhttp://master-ip:8089你会看到“Workers”区域显示已连接的Worker数量。配置用户数和增速并启动测试Master会自动将任务分配给所有Worker。注意事项分布式模式下确保所有Worker节点的系统时间同步使用NTP否则统计图表的时间轴可能会错乱。另外如果压测的目标服务器有IP白名单限制需要确保所有Worker节点的出口IP都在白名单内。5. 结果分析与性能问题定位实战测试跑完了面对一堆数据我们该如何解读如何从这些数字中定位系统的性能瓶颈这比单纯执行测试更重要。5.1 核心性能指标解读我们回到Web UI的“Statistics”页面重点关注以下几列失败率Failures / Requests这是第一道红线。如果失败率尤其是非200状态码的失败随着压力上升而显著增长说明系统已经无法正确处理请求可能出现了服务不可用、资源耗尽或代码bug。需要立即查看“Failures”标签页确认错误类型。响应时间百分位数P95, P99平均响应时间可能掩盖问题。假设100个请求99个是10ms1个是10s平均响应时间大约是109ms看起来还行。但P95是10msP99却高达10s这意味着有1%的用户体验极差。P95和P99是衡量系统稳定性和一致性的关键。它们的大幅上升通常意味着系统某些组件如数据库、缓存、外部API达到了瓶颈。RPS每秒请求数曲线在“Charts”标签页观察RPS曲线。在用户数持续增加的情况下如果RPS曲线达到一个平台后不再增长甚至下降而响应时间却急剧上升这就是典型的系统吞吐量瓶颈。系统已经满负荷无法处理更多请求。用户数与响应时间关系观察“Charts”中用户数增加时响应时间特别是P99的变化。理想情况下响应时间应保持平稳或缓慢上升。如果出现拐点即用户数达到某个阈值后响应时间直线上升这个阈值就是系统在当前配置下的最佳并发用户数。5.2 常见性能瓶颈模式与排查思路根据测试结果我们可以初步判断瓶颈可能出现在哪里模式A低并发下响应时间就很高且RPS上不去。排查思路这通常不是并发问题而是单次请求处理本身就慢。重点检查应用代码是否有慢SQL查询是否在循环中进行远程调用可以使用应用性能监控APM工具定位。数据库查询是否缺少索引表数据量是否过大检查数据库慢查询日志。外部依赖调用的第三方API或微服务是否响应缓慢在脚本中单独压测这个外部接口。Locust辅助在脚本中为不同的请求步骤如“查询数据库”、“调用API”分别打点并命名使用name参数可以在统计页面看到每个步骤的独立耗时帮助缩小范围。模式B随着并发增加RPS先线性增长后持平响应时间缓慢增长。排查思路这是比较健康的模式说明系统有一定弹性。瓶颈可能在于应用服务器资源检查CPU使用率是否接近100%内存使用是否过高是否存在频繁的Full GC如果是Java应用。Web服务器/连接池Nginx/Apache的Worker进程或连接数是否已满应用服务器的线程池或数据库连接池是否耗尽系统限制检查压测机和服务器的ulimit -n文件描述符限制、网络带宽是否打满。模式C达到某个并发点后RPS骤降失败率飙升响应时间暴涨。排查思路系统可能发生了雪崩。常见原因数据库锁或死锁高并发写操作可能导致锁竞争激烈。缓存击穿/雪崩大量请求同时查询一个不存在或过期的缓存Key直接打到数据库。服务依赖链中某个环节宕机微服务架构中一个下游服务崩溃会导致上游服务大量超时和失败。Locust辅助结合系统的监控告警如数据库活跃连接数、缓存命中率、服务错误日志一起分析定位最先出现异常的组件。5.3 生成与解读测试报告Locust Web UI的数据是实时的但我们需要一份静态报告用于归档或分享。Locust本身不直接生成漂亮的HTML报告但可以通过以下方式获取数据下载CSV测试结束后在Web UI的“Download Data”标签页下载“Statistics”和“Failures”的CSV文件可以用Excel或数据分析工具进行深入分析。使用--csv参数运行在无头模式运行时可以使用--csv参数前缀来指定CSV文件的基础名称Locust会定期将统计数据写入文件。locust -f locustfile.py --hosthttps://example.com --headless -u 1000 -r 100 -t 10m --csvresult这会生成result_stats.csv、result_failures.csv等文件。结合Python的pandas和matplotlib库可以轻松绘制出更定制化的图表。集成第三方报告工具社区有一些插件如locust-plugins提供了连接InfluxDB或TimescaleDB的监听器可以将数据实时写入这些时序数据库然后通过Grafana搭建强大的实时监控看板。这也是将性能测试数据融入整个运维监控体系的好方法。实操心得我习惯在每次压测后不仅保存CSV数据还会对Web UI的关键图表进行截图特别是RPS和P99响应时间曲线并附上一段简短的结论如“在500并发用户下系统P99响应时间稳定在200ms以内符合SLA要求”。这份简单的记录对于后续的性能回归对比非常有价值。6. 进阶技巧与生产环境实践掌握了基础用法后一些进阶技巧能让你的压测更贴近真实场景也更高效。6.1 模拟更真实的用户行为思考时间Wait Time的学问between(1,5)这种均匀分布可能不够真实。用户操作间隔更可能符合正态分布或指数分布。Locust提供了constant_pacing和constant但对于更复杂的分布你可以自定义wait_time方法。from locust import HttpUser, task import random class RealisticUser(HttpUser): def wait_time(self): # 模拟指数分布大部分等待时间较短少数较长 return random.expovariate(1.0) # 参数lambda1.0平均等待1秒 task def my_task(self): self.client.get(/)用户分类与比例你的系统可能有多种用户角色比如浏览者、购买者、管理员他们的行为模式截然不同。你可以定义多个HttpUser子类并通过--class-picker命令行参数需自定义或在脚本中通过权重控制比例。更常见的做法是在一个用户类里通过tasks属性组合不同权重的TaskSet来模拟混合流量。参数化与数据驱动避免所有用户使用相同的数据这可能导致缓存命中率虚高。可以从CSV、JSON文件或数据库中读取测试数据。import csv from locust import HttpUser, task, between class DataDrivenUser(HttpUser): wait_time between(2, 5) def on_start(self): # 假设有一个users.csv文件包含username和password with open(users.csv, r) as f: reader csv.DictReader(f) self.users list(reader) self.current_user random.choice(self.users) task def login_and_view(self): # 使用随机选取的用户数据 self.client.post(/login, jsonself.current_user) self.client.get(/profile)确保你的数据量足够大避免在长时间压测中重复使用相同数据。6.2 集成到CI/CD流程性能测试左移集成到CI/CD流水线中可以在代码合并前就发现性能回归。这通常以无头模式运行。一个简单的GitLab CI.gitlab-ci.yml配置示例stages: - test performance_test: stage: test image: python:3.9-slim before_script: - pip install locust script: - locust -f locustfile.py --host$TARGET_HOST --headless -u 50 -r 5 -t 1m --only-summary rules: - if: $CI_COMMIT_BRANCH main # 仅在合并到主分支时运行这里使用了--only-summary参数它会在测试结束后在控制台输出一个简明的摘要方便流水线判断测试是否通过。你可以在script环节后添加逻辑例如解析输出如果失败率或P99响应时间超过某个阈值就以非零状态码退出从而使CI任务失败。6.3 监控压测机自身资源在发起高并发压测时压测机Locust Worker本身也可能成为瓶颈。你需要监控压测机的CPU使用率如果持续高于80%可能无法有效产生足够压力。内存使用量每个Locust协程用户会占用一定内存。模拟数万用户时需要注意。网络带宽使用iftop或nethogs工具查看网络吞吐量是否打满。可用端口数每个并发连接需要一个本地端口。检查net.ipv4.ip_local_port_range范围是否足够以及TIME_WAIT状态的连接是否过多。可以适当减少net.ipv4.tcp_fin_timeout的值来加快端口回收。如果压测机资源不足就需要采用前面提到的分布式模式增加Worker节点。踩过几次坑之后我个人的体会是性能测试不是一个“跑完就完”的任务而是一个“观察-分析-调整-再验证”的循环。Locust给了我们一把灵活且强大的锤子但更重要的是我们要知道用它敲打系统的哪些部位以及如何解读敲打产生的声音。从编写一个简单的脚本开始逐步增加复杂性贴近真实业务流同时密切关注系统监控指标你就能越来越熟练地使用Locust来为你的系统稳定性保驾护航。最后一个小技巧对于重要的性能测试场景建议将Locust脚本、测试数据和关键的运行参数如并发数、时长一起纳入版本控制这样任何时候都能复现当时的测试条件进行准确的对比。