从零搭建自动化测试平台:架构设计、核心模块与落地实践 在测试这个行当里待得久了你会发现一个规律刚上手时大家比拼的是用例设计能力写得出覆盖全面、逻辑严谨的用例就是高手但做到后期真正的分水岭往往是工程化能力——能不能把零散的脚本变成一套可持续运转的系统能不能让团队里每个人都能低成本地创建、执行、跟踪自动化测试而不是每次都要翻某位同事的电脑才能跑起来。这也是我决定从零搭建一套自动化测试平台的原因。与其在多个工具之间来回切换、靠人工收集报告不如把用例管理、任务调度、执行机调度、报告展示、质量数据统计这些能力收拢到一个统一入口里。它解决的核心问题有三个让测试执行不需要依赖个人环境让测试结果沉淀成可视化数据让回归测试可以按计划自动触发。这篇文章我会把这个平台的完整搭建过程、技术选型逻辑、核心模块的设计思路以及我在实际落地过程中踩过的坑一次性讲清楚。适合正在做自动化测试建设、或者打算自己动手搭一套内部测试平台的团队参考。1. 整体设计思路与方案选型1.1 先想清楚平台要解决什么问题很多团队在建设自动化测试平台时容易犯一个毛病一上来就追求大而全要在线编写脚本、要实时日志流、要做各种炫酷的仪表盘结果项目拖了几个月还停留在 Demo 阶段。我个人的建议是先界定 MVP最小可行产品边界把“必须线上化”的能力先做掉其他能力逐步迭代。测试平台最核心的闭环其实只有四件事用例怎么存、任务怎么跑、结果怎么收、报告怎么看。围绕这四条主线去拆解用例怎么存需要一个结构化的用例仓库支持按项目、模块、接口或页面维度组织。任务怎么跑用户发起一次测试执行平台能把用例分发到可用的执行机上跑起来并收集过程数据。结果怎么收执行过程中产生的日志、截图、断言结果、耗时等数据需要统一采集。报告怎么看执行完成后自动生成聚合报告支持失败定位和历史对比。我当时的设计原则是“平台只做调度和展示执行交给独立脚本”。这样平台的代码逻辑相对简单不需要在平台进程里跑测试逻辑一旦某个用例执行器挂了也不至于拖垮整个平台。1.2 技术选型要考虑团队维护成本选型这事没有标准答案但有两条原则很关键用团队最熟悉的技术栈优先选择生态成熟的组件。我最终定的方案是后端使用 Python 的 FastAPI 框架提供 REST API。前端使用 Vue 3 Element Plus搭建管理界面。数据库使用 MySQL用于存储用例、任务、执行记录等结构化数据。缓存使用 Redis用于存放待执行任务队列和执行机心跳信息。调度器使用 APScheduler处理定时任务触发。执行机 Agent 使用 Python 编写通过长连接或轮询方式向服务端领取任务。这里有个值得展开的点为什么调度器不直接用现成的 Jenkins其实我也考虑过基于 Jenkins 去做二次开发但后来发现 Jenkins 在“用例级调度”这个粒度上并不友好——Jenkins 的 Job 概念更偏向于一条流水线而不是一组可灵活筛选的用例集合。我们的场景是用户勾选若干个用例动态组成一个任务去跑用自研调度器反而更灵活。当然如果团队对 Jenkins 非常熟用它做外围的定时触发、再回调平台 API 也是一种可行方案这个没有必须二选一只要接口分开就行。前后端分离后执行机 Agent 独立部署通过注册机制接入平台。这样整个系统在物理上分成了三层平台服务端、数据库/缓存等基础设施、执行机集群。我在实际部署中用一台 8C16G 的服务器承载平台服务端和 MySQL/Redis执行机则按需在测试环境机器上部署 Agent。2. 核心模块解析与交互流程设计2.1 用例管理模块用例管理是一个测试平台的“地基”。我在设计时没有把用例的数据结构搞得太复杂考虑到自动化测试用例通常归属于某个项目、某个模块并且自身有名称、优先级、类型、脚本路径等信息我把用例表设计成了这样字段类型说明idbigint主键project_idbigint所属项目 IDmodule_idbigint所属模块 IDnamevarchar用例名称prioritytinyint优先级P0/P1/P2test_typetinyint用例类型接口/UI/性能script_pathvarchar脚本相对路径creatorvarchar创建人statustinyint启用/停用脚本与用例分离是这里的关键决策。平台只记录脚本路径具体的测试逻辑由仓库中的代码来维护。这样有两个好处一是调试用例时不需要经过平台本地直接跑代码就可以二是执行机拉取最新代码后用例脚本天然同步不用在平台里维护一份代码副本。对于测试脚本的编写我推荐采用Page Object 模式来组织 UI 自动化接口自动化则基于关键字驱动封装一层公共方法。比如接口测试会把“发送请求”“断言响应字段”“从响应中提取变量”封装成关键字用例脚本里只需要描述业务场景不需要重复处理请求细节。这个设计对后续让业务同事参与编写用例也有帮助。2.2 任务调度模块任务调度这一块是整个平台里最需要细心设计的。表面上看起来只是“触发执行”实际上需要考虑并发控制、超时处理、失败重试、执行机负载均衡。我将调度逻辑分成了两部分定时触发使用 APScheduler 创建定时任务到时间点后调用任务创建接口生成一条执行计划。手动触发用户在界面上勾选用例点击“执行”平台创建一条任务并放到 Redis 的等待队列里。执行机 Agent 启动后会定期向服务端注册心跳上报自己的存活状态、当前是否空闲、执行容量等信息。调度器在分发任务时会从“在线且空闲”的执行机列表里选择合适的机器把任务数据发过去。这里要特别说明一下任务的执行状态流转。我把任务状态设计为等待中 → 执行中 → 成功/失败/超时。其中“超时”这个状态非常有用它由服务端侧的任务超时监测机制触发——每个任务在创建时会设定一个最大执行时长超过这个时长还没收到完成回执服务端直接判为超时并允许重试或标记失败。这样即使执行机 Agent 崩溃任务也不会一直卡死占用队列资源。2.3 执行机 Agent 的设计Agent 是执行任务的“手”我把它设计成尽量轻量。它只做四件事启动时向服务端注册上报执行机 ID、IP、支持的测试类型、当前状态。周期性发送心跳同时作为获取新任务的一种机制。从服务端拉取分配给自己的任务调用本地的执行命令来跑测试。执行过程中上报日志片段执行结束后回传结果文件路径和汇总数据。Agent 与平台通信采用 HTTP API没有用 WebSocket。原因很简单HTTP 轮询的实现成本最低出问题也最容易排查。虽然实时性差一些但对于测试任务这种“分钟级”粒度的场景完全够用。实际执行时Agent 会在本地创建一个工作目录拉取用例仓库指定分支的代码然后通过命令行执行测试框架接口测试我用 pytestUI 测试我用 pytest Selenium并把 pytest 的 JUnit XML 报告和日志文件上传到平台的文件存储中。执行命令的具体构造我放在了服务端返回的任务数据里这样调整执行参数时不需要升级 Agent。2.4 报告与数据统计执行完成后平台需要把结构化结果展示出来。我的做法是Agent 回传一份 JSON 格式的结果摘要总用例数、通过数、失败数、耗时平台把它写入数据库同时原始日志与截图上传到文件目录供用户进一步下载查看。报告页面上我会展示五块信息本任务的执行概况卡片通过率、总耗时、执行机、执行分支。用例明细列表每条用例的状态、耗时、失败原因、日志链接。失败趋势折线图最近若干次任务的失败率走势。耗时分布各用例耗时排序找出拖慢回归的“钉子户”。错误类型聚合把常见异常信息聚类快速定位是环境问题还是代码问题。这块我可以推荐两个组件图表直接用 ECharts表格用前端组件自带的分页排序即可。数据查询接口按任务 ID 维度和时间维度各写一个前端按需调用。3. 实操过程与关键环节实现3.1 搭建项目骨架我建议不要一上来就写业务代码先把项目工程建好再逐层填充模块。整个服务端代码目录我大致是这样组织的test-platform/ ├── api/ # 路由层 │ ├── projects.py │ ├── cases.py │ ├── tasks.py │ ├── agents.py │ └── reports.py ├── core/ # 核心逻辑 │ ├── scheduler.py # 定时调度 │ ├── dispatcher.py # 任务分发 │ └── executor.py # 任务执行状态机 ├── models/ # ORM 模型 ├── schemas/ # 请求/响应模型 ├── services/ # 业务逻辑 ├── storage/ # 文件存储 └── main.py # 应用入口数据库表之间的核心关系可以这样概括项目 → 模块 → 用例任务 → 任务用例关联表 → 执行记录。我建了大概 12 张表这里不全部贴 DDL只把最关键的任务表结构展示出来因为它承载了整个调度的主流程CREATE TABLE test_task ( id bigint NOT NULL AUTO_INCREMENT, task_no varchar(32) NOT NULL COMMENT 任务编号, name varchar(128) NOT NULL COMMENT 任务名称, type tinyint NOT NULL COMMENT 任务类型1-手动 2-定时, status tinyint NOT NULL COMMENT 状态0等待 1执行中 2成功 3失败 4超时, trigger_type varchar(16) DEFAULT NULL COMMENT 触发方式manual/cron, total_cases int DEFAULT NULL COMMENT 用例总数, pass_cases int DEFAULT NULL COMMENT 通过数, fail_cases int DEFAULT NULL COMMENT 失败数, executor_ip varchar(64) DEFAULT NULL COMMENT 执行机IP, start_time datetime DEFAULT NULL, end_time datetime DEFAULT NULL, create_by varchar(64) DEFAULT NULL, PRIMARY KEY (id), KEY idx_task_no (task_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有个容易踩的细节任务编号我用了日期随机数的格式比如T20250518153001A8K3。不要直接用自增 ID 当任务编号暴露给用户不然很容易被猜到总量也容易被外部批量调用。任务编号最好由服务端统一生成并且加上一天内的唯一性校验。3.2 任务分发链路实现任务分发是整个系统最核心的执行链路。我在 dispatcher.py 里实现了一个轮询调度器简单说就是从 Redis 队列取任务按顺序分配给当前在线的执行机。核心逻辑伪代码如下def dispatch_task(task_data): online_agents get_online_agents() available [a for a in online_agents if a.is_idle] if not available: mark_task_waiting(task_data.task_no) return agent available[0] # 简单轮训也可以用权重 send_task_to_agent(agent, task_data) update_task_executor(task_data.task_no, agent.ip) mark_task_running(task_data.task_no)我在实际实现中并没有使用什么复杂的负载均衡算法最初版本就是简单的列表轮询。因为执行机的任务执行时间通常在几分钟到几十分钟不等相比高并发场景根本不是一个量级简单的轮询已经能保证均衡。Agent 端拉取任务的代码我用 Flask 写了一个轻量接口长这样app.route(/api/v1/agent/task/poll, methods[POST]) def poll_task(): agent_id request.json.get(agent_id) agent_ip request.json.get(agent_ip) task get_task_for_agent(agent_id) if not task: return jsonify({code: 0, data: None}) return jsonify({code: 0, data: task})Agent 轮询间隔我设置为 5 秒。这个间隔经过了实际测试再短意义不大徒增服务端压力再长会让任务分发有延迟感用户在页面上点完执行要等 10 多秒才有反应。5 秒既能保证感知上的即时性也给服务端留了余量。3.3 执行机 Agent 的实现细节Agent 收到任务后的执行流程我整理成了一个顺序表创建本次任务的本地工作目录命名格式为{task_no}_{timestamp}。拉取用例仓库指定分支的最新代码如果仓库是 Git 管理的先执行git fetch和git checkout。根据任务类型选择执行方式接口用例执行pytest -m apiUI 用例执行pytest -m ui带 pytest 的--junit-xml参数输出结构化报告。执行过程中将日志实时写入本地文件。执行结束后解析 JUnit XML统计出通过、失败、跳过、耗时等数据。将日志、截图等产物打包上传到平台存储。调用服务端回执接口上报结果摘要任务状态流转完成。执行机环境是接触自动化测试平台的人最容易忽略的一环。我在搭建平台之前先花了一周时间把执行机的统一环境搞好Python 版本统一为 3.9浏览器统一装 Chrome 并锁定版本用 Selenium 时必须保持 ChromeDriver 与浏览器版本匹配依赖用 requirements.txt 锁定版本并通过虚拟环境隔离项目依赖。执行机不要装一堆无关软件也不要让多个项目共享同一个环境目录干净的执行机是自动化稳定的前提。3.4 定时任务与自动化触发定时任务是自动化测试平台的重要价值所在特别是对于需要每天凌晨跑回归的团队。我在 API 层暴露了定时任务的 CRUD 接口前端页面上用户可以创建一个“定时任务”指定 cron 表达式和执行范围。例如每晚凌晨两点执行一次冒烟测试0 2 * * *创建时平台不会直接依赖系统 crontab而是把这条规则写入数据库的 cron 表然后由调度器进程扫描数据库中的 cron 配置在内存中注册到 APScheduler。这里有个坑我需要提醒一下APScheduler 的时区一定显式设置为本地时区不要依赖系统默认。有次我在服务器上部署后定时任务总是在比预期晚 8 小时触发排查半天发现是调度器默认读取 UTC 时间而业务上期望的是北京时间。这个属于“看似不可能但确实会发生”的问题。此外定时任务触发失败后要有告警。我在设计里把告警分成两层任务触发失败告警和任务执行失败告警。前者是调度器调接口异常后者是执行结果里失败率超过阈值。告警渠道选择了企业微信机器人 Webhook在通知里带上任务编号、失败率、日志链接团队查问题不用再登录平台搜索。4. 常见问题与排查技巧实录4.1 Agent 状态显示“离线”但机器明明是活的这是平台搭建初期最频繁遇到的问题。现象是页面上的执行机列表显示离线但 SSH 到执行机查看Agent 进程明明在跑日志也没有异常。排查思路分三步先看 Agent 到服务端的网络连通性在 Agent 机器上用 curl 请求服务端心跳接口确认不是防火墙拦截。打开服务端日志查看最近一次心跳处理记录。如果服务端根本没收到心跳请求问题大概率出在 Agent 侧的网络或代码如果收到了但心跳时间戳比较旧说明 Agent 处理心跳的循环卡住了。检查数据库中心跳时间字段是否有索引。如果没有索引执行机数量多了以后时间查询会变慢导致服务端判断心跳超时。最终我们发现的原因非常“经典”Agent 的日志里抛出了一个数据库连接超时异常导致心跳上报线程中断。解决方法是把 Agent 的心跳上报设计成独立线程并且加上异常捕获和自动重连机制——任何异常都不能让心跳线程退出。4.2 用例执行失败率突然升高但脚本本地跑没问题这种问题在测试平台上线后会让人很崩溃。本地执行明明全部通过一放到平台上跑就有几十条失败打开日志一看全是超时、连接拒绝。我总结的排查思路是先怀疑环境再怀疑脚本。确认执行机上是否缺少依赖包用pip list对比本地环境。确认被测服务的地址是否不同。平台执行机很可能不在你的本机网段无法访问 localhost。确认测试数据是否存在。本地调试时数据库里可能有调试数据新的执行机连的是另一个数据库里面没有测试数据。确认执行机时区、系统语言是否影响程序逻辑。我们的项目就遇到过类似问题接口用例里写死了某个环境地址本地能通执行机网络策略不通导致所有依赖该地址的用例全部失败。后来我在用例表里增加了“环境标识”字段执行时通过环境变量注入不同的 BaseURL从根上解决了这个问题。这也是我建议所有接口测试用例把环境地址参数化的原因。4.3 任务在队列里一直显示“等待中”不被执行这个问题的排查点相对集中。任务状态一直是“等待中”说明任务没有被分发出去最可能的原因是没有在线且空闲的执行机。我建议在管理页面上加一个“执行机负载”视图直接展示每台执行机的当前任务数、最近心跳时间、状态。这样当任务“卡住”时运维人员一眼就能看到是不是执行机队列堵住了。另一个可能原因是轮询调度器卡在了某个网络请求上。我在 dispatcher 里所有发送任务到 Agent 的请求都加了超时时间默认 3 秒超过直接抛异常并记录日志不会影响下个任务的调度。4.4 定时任务没有触发定时任务不触发排查看两个点调度器进程日志和数据库中的 cron 配置。有次我们修改了一条 cron 规则但调度器一直没有生效。排查后发现问题出在调度器没有定时刷新数据库的 cron 配置——APScheduler 的 job 是在内存里管理的必须显式调用刷新逻辑才能加载新增或修改的 task。后来我在调度器里加了一个轮询数据库 cron 配置的循环每 30 秒检查一次是否有新配置或配置变更保证改动增量同步这个问题就不再出现了。4.5 报告打开速度慢报告页面的查询慢一般跟数据量和查询方式有关。执行记录表会随着每天定时任务执行越来越多如果不做归档或清理半年后查询接口可能就需要 20 秒甚至更久。我的解决思路是执行记录表定期归档超过 6 个月的数据转存到历史表。列表页查询默认只查最近 30 天并提供筛选条件。为高频查询字段任务编号、执行时间、状态建联合索引。报告页的统计数据单独缓存到 Redis避免每次请求都实时聚合。这个优化做完后报告打开速度从原来的十几秒降到了 2 秒以内体感提升非常明显。如果团队里有人反馈“报告开得很慢”先排查这几个点即可。5. 平台落地过程中的经验心得5.1 先定流程再写代码很多开发者在搭建测试平台时容易从代码写起写着写着才发现流程没定清楚比如执行机的权限谁来管理定时任务由谁审批用例代码和平台数据之间的对应关系如何维护这些问题如果没有事先想清楚平台做到一半就很容易推翻重来。我在项目启动阶段花了一周时间梳理流程包括谁可以创建用例、谁可以发起执行、执行机新增需要走什么申请流程、定时任务通知发给谁。流程定了之后再用代码去承载它平台才会真正贴合团队的运作方式。5.2 用例脚本的版本管理一定要跟平台分开我一直强调脚本仓库与平台数据分离。平台里的用例表只存元数据名称、模块、路径不存代码内容。代码统一放在 Git 仓库中执行机拉取时使用固定分支。这样平台升级不影响脚本维护脚本改动也不需要动平台数据结构。如果想把用例代码和平台深度集成比如在线编辑脚本、在线调试那要投入的工程量会大很多并且调试体验通常不如本地方便。个人建议除非是平台产品化的需求否则没必要做在线编辑器给测试同学提供本地开发 平台执行的模式就足够高效了。5.3 平台上一定要有“可观测性”这里的可观测性指的是任何一个任务的状态变化都要能在平台上查到痕迹。我做的具体事情是给任务表加了一张操作日志表记录任务的每次状态变更谁创建、谁调度、分发到哪台机器、执行开始、执行结束、失败原因等。这样一旦用户反馈“任务没跑”“结果不对”你打开日志表就能完整还原发生了什么而不需要去执行机上看本地日志。这个设计投入很小但对排查问题效率的提升是巨大的。可以说自动化测试平台里最有价值的部分往往不是炫酷的前端界面而是那些能让问题快速定位的数据记录。6. 后续扩展方向从“能跑”到“好用”平台上线稳定运行之后我开始思考下一步的迭代方向。如果只停留在“用例能跑、报告能看”的阶段那它本质上还不算是一个好的平台产品只是个工具的组合。真正的平台应该能够帮助团队提升测试效率和质量。我目前规划的扩展方向有三个数据驱动与关键字驱动的深化。现在的用例脚本依然需要编写代码后续要考虑让业务测试人员通过平台低代码方式编写用例比如通过拖拽组件配置接口请求、断言规则、参数提取等。这样能大幅降低自动化测试的参与门槛。与 DevOps 链路深度集成。目前的定时任务还比较基础后续可以对接持续集成流水线在构建完成后自动触发测试任务并将测试结果回传给流水线控制发布流程。这块的核心是提供一套稳定的开放 API让外部系统能够灵活调用。质量趋势分析。用例运行一段时间后平台会积累大量历史数据。通过分析这些数据可以统计出不同模块的失败率变化、环境稳定性指标、用例耗时排行帮助团队定位高频失败模块并优化测试策略。这一步需要数据仓库和可视化能力更进一步的建设。扩展的优先级我会倾向于先把“低代码用例编辑”这块做起来因为它能直接扩大自动化测试的覆盖面让更多角色参与进来而不是让自动化变成少数几个开发者的专属工具。如果你所在的团队也正在经历测试脚本散落在个人电脑、回归需要手动触发、执行结果难以追溯的阶段希望这篇文章能给你一些参考。搭建自动化测试平台不是一件能一蹴而就的事但只要抓住了“用例管理、任务调度、执行分发、结果展示”这条主线一步步迭代它带给团队的效率提升一定值得投入。