ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

虚拟教育架构中的自动化测试:四个步骤从质量保障到架构优化

虚拟教育架构中的自动化测试:四个步骤从质量保障到架构优化 开学季那天晚上十一点半运营群里突然炸了。老师端布置作业的接口持续超时学生端同步进度的按钮一直转圈用户反馈截图刷了几百条。等我们排查完发现问题出在一次看似无关的权限策略改动上时已经是第二天早上。那一刻我彻底想明白一件事虚拟教育架构里最贵的不是服务器是我们对系统到底哪里会坏这件事的盲目。我做自动化测试其实不算早真正下决心是在那两场事故之后。当时我们负责的虚拟教育平台有Web端、小程序、iOS和Android四类入口背后连着直播、录播、题库、作业、学习记录五套核心服务每两周发一个版本。发布前靠测试同学手工回归上万个用例点靠人肉点根本点不完。于是我开始系统地把自动化测试铺进虚拟教育架构里前后做了四个步骤这篇文章完整记录的就是这个过程以及我在过程中踩过的坑、推翻过的方案、最后沉淀下来的东西。标题说四个步骤其实核心就一句话先让问题能被稳定重复地发现再让发现问题的过程自动化最后让自动化结果变成架构优化的依据。下面我按实际推进的顺序写不绕弯子该上代码的地方直接上代码。1. 虚拟教育平台的崩溃时刻自动化测试从可有可无变成保命工具1.1 开学季那两场事故的教训第一场事故是老师端布置作业超时。当时我们优化了作业附件上传的存储策略把文件从旧的对象存储迁移到新的桶里结果只改了上传服务的配置忘了同步更新作业草稿箱的权限校验逻辑。老师保存草稿时权限策略返回了一个低级的权限错误前端没有兜底处理直接表现为转圈卡死。这类问题如果只是单个接口报错手工点一次可能也能发现但它只发生在作业附件大于10MB老师角色草稿箱已有内容这个组合条件下手工回归很难覆盖到。第二场事故更典型。学生端学习进度同步接口在一次鉴权组件升级后出现了偶发的会话失效。十分钟内几百个学生同时提交学习记录一部分请求拿到了新的会话一部分拿的还是旧token网关层对混合请求的处理策略不一致导致同一节课的学习进度被相互覆盖。用户看到的是学过的课突然变回未学状态。这种数据一致性问题比功能报错更隐蔽因为它不报错只产生错误的静默结果。这两场事故的共同点是什么都不是什么高深的技术问题都是改动之间的连带影响没被发现。虚拟教育平台和一般业务系统最大的区别在于它的核心数据模型有强烈的过程连续性——学生从选课、看视频、做练习、交作业到拿成绩是一条完整链路任何一个中间环节的数据被污染下游全部失真。这种架构里回归测试的价值远大于功能测试因为真正的风险从来不在单点功能而在链路耦合。1.2 为什么人工回归撑不住了在自动化测试之前我们团队采用的是核心用例人工冒烟重点功能手工回归的模式。刚开始还行系统功能少测试同学把主要流程走一遍用不了半天。但虚拟教育平台有一个很特殊的地方它天然是多角色、多终端、多状态的系统。多角色是指学生、老师、助教、教务管理员、机构超级管理员每个角色看到的界面和可用操作完全不同。多终端是指同一套业务要跑在Chrome、Safari、微信内置浏览器、iOS App、Android App上光是真机兼容测试就要准备一大排设备。多状态则是指同一门课程可能处于未开课、报名中、进行中、已结课、归档五种状态作业可能处于草稿、已发布、批改中、已退回、已归档五种状态两个维度一组合测试场景数直接爆炸。我算过一笔账假设我们只覆盖学生和老师两个角色的核心主链路每个角色跑10个大场景每个场景平均需要20步操作那一次完整回归就是400个操作点。每个操作点算上等待时间大概5秒就是2000秒差不多35分钟。这是最理想的情况实际上加上网络波动、页面加载、数据准备一个测试同学完成一轮核心回归至少要半天。而且这半天结束后她能告诉你这些场景没报错但没办法告诉你哪些接口响应变慢了哪些数据被覆盖了哪次变更影响了多少用户——这些信息才是架构优化真正需要的。所以我要做的第一件事不是急着写脚本而是先建立一套能让问题稳定暴露的基线。没有基线自动化测试写出来也是东一榔头西一棒子。2. 第一步重建测试基线让每次发版都有可量化的回归依据2.1 先画核心链路地图不画地图不写脚本很多人做自动化测试的第一步是选框架、搭环境我恰恰相反。我做的第一件事是拉着产品和后端同事把整个虚拟教育平台的用户主链路画了出来。不是那种高大全的系统架构图而是一张用户从进入系统到完成学习目标经过的所有关键节点的流程图。以学生为例主链路是登录→进入课程列表→查看课程详情→报名/选课→进入学习空间→播放录播课/进入直播课→做随堂练习→提交作业→查看成绩→生成学习报告。以老师为例主链路是登录→创建课程→上架课程→维护课程内容→布置作业→批改作业→发布成绩→查看学情统计。每条链路我都要求标注出涉及的微服务、核心表、关键接口、缓存策略和消息队列。这张图的价值在后来的测试设计里反复体现。比如我们梳理后发现几乎所有学生主链路都会经过课程详情页这个节点而课程详情页同时依赖课程服务、价格服务、库存服务和用户学习进度服务四个后端服务。这个页面如果挂了影响的不是单个功能而是整个学习入口。所以我把课程详情页请求链路列为最高优先级的自动化监控项专门为它写了接口级的链路断言脚本。链路地图画完之后我带着团队把点位分成了三层。第一层是P0主干链路登录、选课、看课、交作业这些断一条就是事故的路径第二层是P1重要功能涉及权限、支付、消息通知等横向能力第三层是P2边缘场景包括异常输入、弱网、断点续传等。这个分级直接决定了后面自动化用例的编排顺序和执行策略。2.2 测试环境与测试数据的洁癖式管理基线光有链路还不够必须有稳定可控的测试环境和数据。虚拟教育平台最容易坑人的就是环境很多机构会部署私有化版本测试环境、预发环境、生产环境的鉴权体系、存储配置、第三方回调地址都不一样。我们当时做过一次很蠢的事自动化脚本在预发环境跑通后直接切到测试环境跑结果大面积失败。排查了一天最后发现是测试环境的短信验证码服务是mock的而预发环境接的是真实短信网关。这种环境差异如果不提前约定清楚自动化测试第一个星期就会让人怀疑人生。我的做法是对每个环境做了配置化隔离所有环境相关的东西统一放到配置中心不在脚本里硬编码任何URL、账号、密码。每套环境维护一套独立的测试数据并且给测试数据设计一套命名规范。虚拟教育系统里最典型的就是测试账号我用stu_auto_001到stu_auto_010来标识学生自动测试账号tea_auto_001到tea_auto_005标识老师自动测试账号数据初始化脚本会在每次全量回归前把账号下的课程、作业、成绩重置到初始状态。这一步看起来不产生直接价值但它决定了后面所有步骤能不能站住。我见过很多团队自动化测试做不起来不是不会写脚本是环境和数据一团糟脚本每次都要靠运气跑通。这种洁癖式管理看起来很繁琐实际上是在给你自己做心理减负——每次跑测试时你能确信失败是因为代码有问题而不是环境抽风。3. 第二步框架选型不追新按虚拟教育场景做减法3.1 Web端用PlaywrightApp端用Appium为什么这么选到了第二个步骤才开始谈框架。虚拟教育平台的前端形态特别复杂所以UI自动化的选型必须分端讨论不可能一个框架通吃。Web端我最终选了Playwright。理由很实际虚拟教育平台的Web端用户量大、页面交互复杂尤其是视频播放、白板互动、聊天区这些动态元素多的地方对自动化框架的等待机制要求很高。Playwright的自动等待策略是等元素可操作才继续能省掉大量手工写sleep的脏活。而且它天生支持多标签页这对测试学生在一个标签页看直播在另一个标签页查课件这类真实场景非常友好。另外它的trace viewer可以录制完整的执行轨迹出问题时直接把trace文件丢给开发开发自己就能回放看到元素是在哪一步开始定位失败的效率比看截图日志高一截。App端我选了Appium。虽然很多人吐槽Appium慢但虚拟教育机构通常有很多定制化的教学App需求Appium的跨平台能力仍然是最成熟的。我们当时做了一个取舍不在App端做强交互的UI自动化只覆盖核心的登录、课程列表、视频播放启动、作业提交几个关键点把更多精力放到接口自动化上。因为教育类App真正的高频操作其实不多移动端回归的瓶颈往往在兼容性而兼容性靠的是真机矩阵而不是脚本数量。接口自动化我选了Python的pytest加requests这也是我认为最稳妥的组合。pytest的fixture机制很适合处理教育系统里复杂的依赖关系。举个例子测提交作业接口前需要先创建课程、给学生选课、配置作业题目这些前置条件我用fixture串起来测试用例本身只关心提交行为的断言可读性高维护也方便。pytest.fixture def prepared_homework(api_client, create_course, enroll_student, create_assignment): course_id create_course(自动化测试课程) student_id enroll_student(course_id, stu_auto_001) assignment_id create_assignment(course_id, 第一周作业, due_days7) return {course_id: course_id, student_id: student_id, assignment_id: assignment_id} def test_student_submit_homework(prepared_homework, api_client): resp api_client.post( /api/v1/homework/submit, json{ assignment_id: prepared_homework[assignment_id], student_id: prepared_homework[student_id], content: 这是一份自动化提交的作业, attachments: [] } ) assert resp.status_code 200 data resp.json() assert data[submission_status] submitted assert data[submit_count] 13.2 私有化部署环境下的执行器部署细节选框架之外执行环境也要考虑。我们的虚拟教育平台有不少私有化部署的客户客户环境里往往没有外网这意味着自动化测试执行器也得部署在客户的内网环境里。这时候就不能指望把所有东西都放到一个云端集群来跑。我们把执行器做成了Docker镜像包含Python运行时、浏览器、Appium服务、测试脚本和报告生成组件打包后直接部署到客户内网的一台测试机器上。测试任务通过本地的定时调度触发结果生成HTML报告后再由一个内网导出的工具同步到我们的总部看板。整个链路不需要访问外网也不会把客户的教学数据带出来。这里有个必须提醒的坑教育数据合规要求很严自动化测试的数据绝对不能拿真实学生信息来跑。我们内部规定所有自动化账号的数据必须是构造出来的虚拟学员课程内容也必须是测试素材。做私有化客户现场测试时这一条尤其容易踩线因为现场工程师图省事可能直接用客户现成的账号来跑。我的建议是在执行器入口加一道强制校验凡是检测到手机号或者身份证格式的字段直接拒绝执行从机制上杜绝误用真实教学数据。性能测试我们用的是JMeter主要压三个点课程详情页的并发浏览、直播房间的进入请求、以及作业提交的写接口。这三个点对应虚拟教育平台最典型的流量特征——开学选课高峰时详情页浏览会有瞬时洪峰直播开课前十分钟学生集中进入房间作业截止日期前半小时提交量暴增。这几个场景用JMeter做简单的高并发脚本就够了不需要一开始就上复杂的压测平台。4. 第三步把核心教学流程拆成可复用的自动化用例4.1 登录与会话多角色、多终端、多租户的排列组合虚拟教育系统里的登录不是简单的用户名密码校验它通常包含账号体系、租户隔离、微信授权、单点登录、手机验证码多条路径。自动化测试如果全覆盖用例数量会非常庞大所以我用了一个办法把登录抽成公共组件再按角色和入口组合出必要的冒烟用例。我定义了一个login_by_role的公共方法传入角色、终端、租户三个参数返回登录后的会话对象。学生端、老师端、管理员端各自跑一遍Web端登录App端再跑一遍核心登录冒烟大概10个左右用例就能覆盖主要的登录路径。但这些用例的重点不只是能登录成功更重要的是登录后我能访问到该访问的资源访问不到不该访问的资源。这个权限断言是虚拟教育平台自动化测试里最容易被忽略的。举个例子一个初三学生账号本来只能看到自己所在班级的课程。如果后端权限配置出现回归他可能在某次更新后就看到了整个年级的课程列表这在我们这个行业属于高危事故。所以我在自动化用例里专门设计了越权测试场景登录学生A直接请求课程B的详情接口断言返回403或者无权访问标识。这类用例写起来简单但每次发版都能拦住真实事故我们后来拦住的两个权限问题都是靠这种越权断言发现的。4.2 直播课与录播课视频播放链路的专项校验视频播放是虚拟教育架构里最特殊的一环也是最容易出自动化测试假阴性的地方。假阴性就是脚本报了失败但实际上功能是好的。原因在于视频播放依赖CDN、流媒体服务、播放器配置、防盗链签名等多个外部因素任何一个环节慢一点播放器就会进入缓冲状态而自动化脚本如果把播放器进入playing状态作为成功标准就很容易因为网络抖动而误报。我的处理方式是把视频链路拆成三层来做。第一层是接口层直接校验播放地址的生成接口断言返回的CDN地址能正常发起HTTP请求防盗链签名里有正确的过期时间第二层是播放器层判断播放器的加载状态、视频元素上的currentTime是否持续增长这能证明视频真的在播、在推进第三层是体验层统计进入直播间后的首帧耗时、卡顿次数这层不做通过与否的断言只作为趋势数据记录。def test_live_room_enter_success(login_by_role): student login_by_role(student, classic_web) live_room student.enter_live_course(live_math_101) room_status live_room.wait_for_state(playing, timeout15) assert room_status is True # 额外断言播放时间推进避免只进入房间但视频不播 time1 live_room.get_current_time() time.sleep(3) time2 live_room.get_current_time() assert time2 time1这里我踩过一个很值得说的坑一开始脚本只判断房间进入成功没有判断视频时间是否推进。结果直播服务有一次升级后学生能进入房间但播放器始终加载不出画面。因为房间进入接口是正常的脚本全部通过但实际用户的体验已经是黑屏上课。所以做视频相关自动化一定不能只看进入成功要看内容真的在播放。4.3 作业、题库与成绩数据一致性的自动化断言作业和成绩是虚拟教育平台里数据一致性要求最高的两个模块也是自动化测试最能发挥作用的地方。一个学生提交作业后分数要回写到学习记录里同时影响课程完成度、排行榜、结课证书三个下游展示。任何一环没同步用户看到的数据对不上就会产生大量工单。我在作业模块设计了一套端到端的跨服务断言用例。流程是创建课程→给学生选课→老师发布作业→学生提交作业→老师批改打分→学生端查看成绩。每一步完成后都直接查数据库或者调内部服务接口断言数据已经写入并保持同步。# 检查成绩落库 curl -s http://internal-api/learning-record/query -d {student_id: stu_auto_001, course_id: course_001} | jq .homework_score # 检查课程完成度 curl -s http://internal-api/progress-service/summary -d {student_id: stu_auto_001, course_id: course_001} | jq .completion_rate这套用例在写第二个星期就抓到了一个真实问题成绩服务写入新成绩后消息队列异步同步给学习记录服务存在偶发的重复消费导致学生端看到的成绩时对时错。自动化用例跑了五轮第六轮抓住了这个偶发现象配合日志里duplicate_update的异常提示后端同事半天就定位到了消费幂等性问题。这种偶发数据问题手工测试几乎不可能稳定复现但自动化脚本可以挂着反复跑这是它价值最大的地方。内容型课程的自动化则稍有不同重点不是功能而是展示。比如课件里包含大段的课程讲义、代码示例、数学公式这些内容如果展示异常学生是能感知到的但常规功能测试又不会去检查。我们后来引入了一个简单策略对课件页做截图用像素级对比发现页面渲染的异常变化。这个不加白名单会有一堆误报所以只针对固定课程素材、静态环境跑作为内容变更时的辅助检查手段。5. 第四步持续集成串联让自动化测试结果直接指导架构优化5.1 接入CI流水线后的节奏变化自动化脚本全部开发完成后如果没有稳定的触发机制很快就会变成有时间才跑一下的摆设。我的第四步是把自动化测试接入持续集成流水线让它在每次代码合并后自动执行并让结果直接影响发布决策。我们用的是GitLab CI流水线设计成三档。第一档是提交触发每次开发者合并代码到develop分支自动执行接口冒烟用例大概100个用例10分钟内跑完失败就钉住合并。第二档是每日全量回归每天凌晨三点跑UI自动化加接口全量用例早上九点前输出测试报告到团队群。第三档是发布前硬门禁凡是上生产的版本必须全量回归通过率不低于99%并且核心P0用例零失败否则不允许打生产包。这里有个改变团队习惯的关键动作我把测试报告做成了趋势曲线每天记录用例总数、失败数、失败模块分布、接口响应时间均值。有了趋势之后大家关注的焦点从今天有没有挂变成了最近一周失败率为什么从0.5%涨到2%。这个变化特别重要因为当你能看到趋势时自动化测试就从质量检查工具变成了架构健康度的体检指标。5.2 测试结果如何转化为架构优化清单自动化测试跑了一段时间后我拿到了一份非常有价值的报告数据。我统计了三个月内的失败用例发现其中大概百分之四十来自同一个根因课程详情页依赖的四个后端服务之间没有优雅降级任何一个服务慢整个页面就卡住。这个结论从用户投诉里发现不了那么快但从自动化测试的失败分布里看得很清楚——每次详情页相关用例失败后面的依赖服务可行性检查都会标黄。基于这个数据我们把课程详情页改造成了部分渲染局部降级的架构价格服务超时就用缓存兜底学习进度服务挂了就先显示上次缓存的数据不阻塞主内容展示。改造完成后同一批自动化用例的失败率直接从原来的每月十几次降到了两三次。这就是我标题里说的用自动化测试优化虚拟教育架构的真实含义——自动化测试不只是抓bug它还能告诉你架构里哪些耦合点值得优先拆。另外自动化测试也帮我们做了容量规划。我一直用JMeter记录直播课进入接口在并发100、200、500三种压力下的响应时间三个月的数据攒下来基本摸清了当前架构的容量上限。后来销售突然签了一个大型培训机构预计同时在线人数翻三倍我们的压测数据和真实负载曲线让容量评估变得有据可依而不是拍脑袋说应该能扛住或者要不加两台机器。6. 踩过的坑和留下来真正有用的经验6.1 三个让我印象深刻的坑第一个坑是断言写得过于严格导致大量假失败。我刚才提过视频播放的误报问题它本质上是断言粒度不对。我现在写断言有个习惯先问自己这条断言是验证用户可感知的体验还是验证我可以访问的后端细节。后端返回200但页面渲染不出来的情况比接口报500更隐蔽所以断言一定要落到用户可感知的层面。第二个坑是测试数据被其他团队污染。刚开始测试账号是我们自己在用后来其他开发为了联调也拿这些账号测试经常把账号下的课程状态改得乱七八糟第二天自动化跑的时候莫名其妙挂一片。后来我做了数据隔离给自动化账号加了仅供自动化使用的标识后端同事的联调账号单独分配测试数据从源头上切断了数据冲突。顺便说一句如果条件允许自动化测试最好跑在独立数据库实例上别和联调环境混用。第三个坑是一开始贪多求全想把所有功能都自动化。我写了大概两百个UI用例后发现维护成本已经高到快撑不住了——每次前端随便改个按钮位置就要同步更新好几个用例的定位器。后来我做了一次大清洗只保留了真正的高价值稳定用例把容易变的、价值不高的UI用例降级为手工抽查。UI自动化做到精而不多接口自动化做到全而稳这个搭配在虚拟教育平台里是我试验下来最舒服的维护节奏。6.2 一个特别有用的技巧给自动化用例加业务描述技术团队写自动化用例习惯性地只写技术断言比如状态码为200元素可见。但我在实践里发现给每条测试用例加上一句业务描述对后期维护和排查问题有奇效。比如一条用例的注释是验证学生提交作业后老师端待批改数量加一这样当这条用例失败时任何人在消息通知里看到这条描述都能立刻明白是哪个业务链路出了问题而不是去看断言表达式猜业务含义。后来我把这些描述统一整理成了自动化测试用例清单作为团队内部的业务回归手册用新同学上手也快了不少。如果你现在也在做虚拟教育平台或者在线学习类系统的自动化测试我最后想说的是不要一上来就追求全自动化先把你系统里最容易出事故的那条链路做成自动化让它每天都跑再逐步把它变成整个架构的体检仪。自动化测试最难的从来不是写脚本而是坚持让结果说话。
返回列表