摸鱼不组 冲刺测试随笔

moon-star-art moon-star-art 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区

从 Lint 全绿到游戏真能走通

QA测试

疙瘩瘟是本组的明末大鼠疫题材多视角文字互动游戏,基于 Ren'Py 8.5.3 开发。玩家从主菜单进入序场,在郎中线、百姓线、武官线中选一条,靠一连串选择改变状态变量(体力、感染、见识、信任、药材),最终走到各自的结局,并把结局登记进结局图鉴。

这类项目的测试有个特殊之处:它几乎没有算法要验,主要验的是「一连串的选择能不能真的把玩家送到那个结局,而且这个结局真的被记进了图鉴」。一次点错、一次跳转失效、一张美术资源的扩展名对不上,玩家看到的都是同一副现象——游戏玩不下去。而 Lint 在这些地方全是绿的。

结合《构建之法》第2章和第13章相关内容,本项目在冲刺阶段逐步建立了从「启动自检」到「三条线走通」再到「结局注册表一致性」的分层测试流程,并把套件接进 CodeArts 流水线做门禁。以下介绍冲刺阶段的测试安排、工具运用、用例文档和实际体会。

一 结合教材认识测试的作用

第2章讲单元测试与回归测试。对应到本项目,最有用的是 2.1.3「单元测试的标准」那八条:

  • (1)在最基本的功能/参数上验证程序的正确性 → 对应本项目的启动自检:引擎是否就绪、状态变量初值对不对、default 有没有被声明两次、美术资源在不在磁盘上。
  • (3)单元测试之后机器状态保持不变,就可以不断重复运行 → 对应结局图鉴 persistent.unlocked_endings 是跨存档持久化的,测试必须自己接管:备份 → 每个用例前清空 → 跑完还原。否则上一轮跑出来的解锁记录会让本轮断言「假绿」,跑一次就把开发机的通关记录改了。
  • (5)保持单元测试的独立性,单元测试的运行、通过、失败不依赖于别的测试 → 这一条在本轮被现实检验得最狠,第六节详述。
  • (7)集成到自动测试框架中 → 对应 run_tests.sh 一个命令、退出码即红绿,可以直接进 CI。
  • (8)单元测试代码必须与产品代码同步保存和维护 → 对应测试组件单独住一个分支,由流水线 git checkout FETCH_HEAD -- .codearts/test/ 取用,队友不需要往自己的分支里合并测试代码,也就不存在「改了一份忘了另一份」。

作者讲义:单元测试与回归测试

2.1.4 讲回归测试:一个模块所有通过的用例构成它的「基准线」,新构建要重跑这组基准线验证有没有退化。我们把它落成了流水线上的 push / MR 事件触发——每次提交都重跑那 61 条基准线。2.2.2「用质量门禁防止回退」是我们把套件接进 CodeArts 的直接理由。

第13章给了测试的分类框架和 V&V:验证(关注内部质量:单元测试、集成测试、静态检查)与确认(关注外部价值:用户验收、端到端场景)。13.1 用切尔诺贝利讲的那段对我们最有启发——监测工具本身也可能存在缺陷,一份失真的测试报告会把判断引向歧途。本项目的对应说法是「Lint 全绿 ≠ 游戏能玩」;13.4.1 的五个测试误区,我们几乎逐条对上了自己踩过的坑。

作者讲义:测试的计划和执行

二 冲刺阶段的测试工作安排

测试工作随着交付范围逐步扩大,六个阶段的重点与产出如下:

阶段测试工作的重点产出
测试准备阶段建立用例格式与断言口径;给 Ren'Py 测试框架踩坑;把玩家存档隔离出去test/README.md 的「硬规矩」清单
组件交付阶段启动自检:引擎就绪、变量初值、重复 default、美术资源是否在磁盘上boot_test.rpy(33 条)
剧情接线阶段入口接线 → 三条支线逐个点到底 → 状态重置 → 结局登记三条线的 *_line_test.rpy
一致性阶段注册表 ENDINGS ↔ 剧情 unlock_ending ↔ 图鉴界面,三方对齐endings_registry_test.rpy
CI 门禁阶段流水线自举:虚拟显示、glibc 前缀、SDK 下载、看门狗、统一判红绿.codearts/test/ci/
收口阶段整套连跑与单跑对照、失败归因、区分真缺陷与伪红本随笔第六节

测试与开发是并行的。郎中线剧情还在写的时候,只依赖引擎状态、变量初值和磁盘资源的启动自检已经能跑;反过来,启动自检全绿也不代表剧情能走通,所以剧情用例分批接入。郎中线那 6 条用例就是「等剧情分支落地一条、就接入一条」的方式加进来的。

每次执行前固定代码提交并保存源码哈希;本地与线上分别记录版本和结果,不把本地通过直接写成线上通过。缺陷记录包含输入、步骤、预期、实际和证据。

三 测试工具的选择和运用

工具选择主要考虑项目技术栈、测试层次和复现成本。

工具实际用途选择原因
Ren'Py 8.5.3 内置测试框架(testsuite / testcase / 参数化 / xfail)模拟玩家点击、走菜单、断言 store 与 persistent引擎原生,能开真游戏跑真实交互;不用外挂浏览器驱动,也不会出现「浏览器版本变了断言就飘」
Ren'Py lint + --error-code语法、未定义变量、重复 config、缺失 image 声明最便宜的一道门。但 lint 默认恒返回 0,必须显式加 --error-code,否则是假绿
Bash(run_tests.sh)建软链、扫孤儿 .rpyc、FIFO 实时日志、看门狗、解析汇总把执行流程统一起来,减少手工整理错误;退出码直接就是红绿
CodeArts 流水线 + CodeArtsBuild 插件每次 push / MR 自动跑套件并判红绿CodeArts 的步骤只能调插件、没有 run:,跑任意命令的正规路子只有编译构建任务
Xvfb + Mesa llvmpipe给无 X server 的 Linux 构建机提供虚拟显示和 GL 上下文Ren'Py 是 SDL 图形程序,没有真 X server 根本起不来
自带 glibc 前缀(ld-linux --library-path)启动 Ren'Py ≥ 8.5 的引擎引擎要 GLIBC_2.30,构建机是 2.28;必须用新 loader 显式启动,把新 libc 塞进 LD_LIBRARY_PATH 会版本错配直接崩
conda-forge 固定包清单(58 包 / 98 MB)+ 前缀占位符替换器固定图形环境依赖,保证每次构建装的是同一批版本漂移是「昨天绿今天红」的常见来源
fetch-renpy-sdk.sh(分片 + 续传 + 重试 + 测速)下载 153 MB 的 Ren'Py SDK构建网络不稳,一次拉不完会前功尽弃
Git(worktree / FETCH_HEAD)取测试组件、做隔离对照实验组件只有一份,且能让多个分支同时跑

工具选择上最重要的一条教训是:工具本身也要当成被测对象。

Ren'Py 的报告器在引擎中途崩掉时,会把没跑到的用例记成「not run」却依然报 PASSED、退出码 0——那是假绿。所以 run_tests.sh 自己解析汇总行,把 not run 也算成失败。另一头,图形环境装不上时如果退回 SDL dummy 驱动,Ren'Py 会退到纯 Python 的 sw 渲染器,实测卡死 238 秒、0 passed,还会报假的 PASSED。假绿比假红危险得多——门禁最怕的就是这个,所以脚本选择快速失败,绝不退回哑驱动。

四 测试用例文档如何组织

文档分三层,各管各的人:

  1. test/README.md——套件的人读:覆盖什么、怎么跑、写用例时的 10 条硬规矩、当前测出来的真实问题。
  2. .codearts/test/ci/README.md——CI 的人读:CodeArts 控制台怎么点、YAML 只认顶层 jobs、怎么不用进控制台就自检 YAML 好坏。
  3. 用例本体——每个 testcase 带 description,断言处不写「应该对」,写具体期望值。

测试报告使用统一的五列表格:编号、所属模块、测试步骤、预期结果、实际结果。以下几条取自本轮实际执行:

编号所属模块测试步骤预期结果实际结果
B03结局注册表查 e_epidemic_death 是否在 ENDINGS7 个郎中结局都注册在注册表里,且都有对应 label失败,不在注册表;该结局 label 也没调 unlock_ending
B09美术资源查 images/prop/prop_boiled_cloth.jpg 能否加载郎中线引用的 21 个资源,每个至少有一个候选文件真实存在失败,候选 0 个。文件在磁盘上,但内容是 PNG、扩展名却是 .jpg
B10资源路径扫 game/ 下所有 .rpy 的 image 声明每条声明的路径磁盘上都真实存在失败,citizen_line.rpy 有 22 处指向不存在的文件
D02郎中线·观察线全程点选到「著书存道」结局 id 进图鉴;体力 4、感染 1、见识 5、信任 1;持有「病案册」单跑通过,整套连跑超时
E03图鉴界面解锁 e_book 后打开结局图鉴界面显示「郎中 · 著书存道」失败,注册表里该结局名是「著书存道」,没有「郎中 · 」前缀
G01分支守卫在没有武官线的支线上跑武官线用例标 xfailed(不适用),而不是 failed通过,9 条武官线用例全部 xfailed,0 误报

预期结果需要独立于实现提前确定。 郎中线观察线的期望值(体力 4、感染 1、见识 5、信任 1、持有「病案册」)是按剧情脚本独立推导出来的,不是把运行结果抄成期望。反过来也有一条铁律:断言如果只是把实现复述一遍,就会把实现里的错误一起复制进测试。

还有一条是「不适用」与「失败」必须分开。三条线分别在三条支线上开发,一套用例要能原样合进每条支线。做法是给每条线的用例加 xfail 守卫,判据是「这条线在本分支上有没有自己的实现」——不能用 has_label,因为每个分支的 script.rpy 里都带着三条线的完整旧实现,has_label 恒为真,守卫会永远失效,结果是每条分支都去跑别的分支的旧版本,红了也不知道是谁的锅。

五 AI 在自动化测试中的作用

这是本轮投入最大的部分,分四块讲。

5.1 把「游戏能玩」翻译成可判定的断言

AI 主要做的是把一句模糊的需求,变成 61 条能自动判定真假的断言,并按层次组织:启动自检 33 条、郎中线 6 条、百姓线 9 条、武官线 9 条、结局注册表一致性 4 条。分层不是平均分的,而是按教材 13.1 的验证/确认划分:前 33 条偏验证(内部状态对不对),后面 28 条偏确认(玩家视角能不能真的走通)。

5.2 踩坑清单:AI 写第一版,我审

Ren'Py 的测试框架有一批反直觉的行为,AI 写第一版用例时把它们几乎全踩了一遍,整理成 10 条硬规矩,例如:

  • 进游戏用 run Jump("xxx"),不要用 run Start("xxx")。Start 走的是 renpy.jump_out_of_context(),会抛 JumpOutException;在主菜单上下文里引擎能接住,在剧情上下文里没人接,整个测试进程直接崩。
  • run MainMenu(confirm=False) 必须先判上下文,而且在菜单上下文里调它会抛 FullRestartException,框架接不住 → 整个 suite 崩、所有用例 NOT RUN。
  • 点完剧情菜单后 advance 就不灵了,要用 skip fast 兜底——选项动作是 Jump,点击会打断这次交互,之后 advance 依赖的 dismiss 事件不再被处理。
  • 别用 assert label xxx。它依赖 reached_labels,而引擎每帧都会清空这个集合,点击之后立刻断言必然误报。
  • setup 里的局部变量 teardown 看不到。testcase / setup / teardown / before testcase 各自是独立执行作用域,跨 hook 传递必须显式写进 store。
  • 删掉 .rpy 源文件要连带删 .rpyc。Ren'Py 只看 .rpyc,源文件删了而 .rpyc 还在,旧用例会照样被加载执行,还会冒出「在你当前这份代码里根本不存在」的幽灵报错。

这些不是抄文档,是实测踩出来的。

5.3 CI 自举:AI 写脚本,我做环境核对

CodeArts 的步骤只能调插件,不能直接写 shell;服务端 YAML 校验只认顶层 jobs,写成 stages: 会被直接拒(DEVPIPE.00015005);Linux 构建机要自建 Xvfb + Mesa;引擎要 glibc 2.30 而机器只有 2.28,于是要自带一份新 libc 并用新 loader 显式启动;SDK 153 MB 要分片续传;引擎有「出完汇总却不退出」的已知挂死,于是给引擎 900 秒总超时 + 15 秒宽限,超了就强杀整个进程组、再按汇总判红绿。这些配置里的每一个判断,都来自实际跑构建拿到的日志。

5.4 AI 的边界:三个被探针推翻的「根因」

这一段是本轮最重要的收获。

AI 很擅长给出一个看起来像根因的解释。本轮它给了我三个:

  1. 序场有 10 段 pause,合计 24 秒,而用例超时预算是 15 秒 → 入口用例必然超时;
  2. before testcase 里的 MainMenu 全量重启之后,入口点击就失灵了;
  3. 前一条用例失败会污染后续用例,于是后面那批超时全是连带效应。

我给每个假说都写了一条探针用例去反测,三个全被推翻:

  • 探针一:在入口处打点计时,实际从点「从头开始」到出现选角界面只用了 0.2 秒。序场那 24 秒 pause 在测试模式下根本不消耗等待。
  • 探针二:完整复刻 before testcase 的 MainMenu 钩子,第二次点击依然 0.1 秒通过。全量重启不是元凶。
  • 探针三、探针四:故意让前一条用例分别以「断言失败」和「点击超时」收场,后一条用例依然 0.07 秒通过。失败不会污染后续用例。

最后是靠一组对照实验才定位到真凶的:把百姓线那一个 suite 拿掉再整套连跑,郎中线的 6 条超时立刻降到 1 条(52 条:37 通过 / 9 xfailed / 6 失败,21.2 秒;原来 61 条要跑 123.2 秒)。也就是说,是百姓线的用例把游戏状态留在了半路,导致后面的入口步骤到不了。

这条经验正对应教材 13.4.1 的误区三(测试代码本身的质量决定检测效能)和 13.4.3 的「保持批判」,也正是 2.1.2 那句「可以让 AI 工具来写单元测试,但是程序的作者还是要对单元测试负责」的具体含义。

还有一个更朴素的教训:我自己写的解析脚本也出过假结论。最初我按 traceback 归因,把 61 条全算成失败——因为报告器在执行阶段的标记都是同一个 [exc]——而报告器自己的 Detailed 树里明明有 38 条 PASSED。凡是「我自己做的判据」,都得拿已知答案反测一遍再用;「没报 = 没问题」和「报了 = 有问题」,这两个方向的可信度并不相同。

5.5 效率

本轮没有开展人工与 AI 辅助方式的耗时对照实验,所以不给「效率提高百分之多少」的数字。能够确认的是:批量场景整理、重复操作脚本、CI 自举脚本、失败归因和材料核对都由 AI 承担;测试结论一律以实际运行记录为准,不以 AI 的解释为准。

六 当前测试结果与项目评述

6.1 多次可复现的实跑

环境:feature/doctor-line @ e283a4d,macOS,Ren'Py 8.5.3。

跑法用例通过xfailed失败耗时
整套连跑6138914123.2 s
整套连跑(移除百姓线 suite)52379621.2 s
单跑 boot3330030.8 s
单跑 doctor_line6501—
单跑 citizen_line9603—
单跑 endings_registry4202—

九分钟和二十一秒的差别不是重点,同一套件跑出同一组数字才是。

6.2 14 条失败的归因

真失败(与单跑结果一致,移除百姓线 suite 后仍红):

失败用例归属原因
boot_endings_registered(e_epidemic_death)实现该结局 id 不在 ENDINGS 注册表里
endings_registry_every_ending_label_registers实现同源:该结局 label 只置了 doctor_complete,从没调 unlock_ending
boot_art_assets_present(prop_boiled_cloth.jpg)资源文件在磁盘上,但内容是 PNG、扩展名却是 .jpg,renpy.loadable() 判不可加载。lint 完全没报这一条
boot_script_image_paths_resolve资源citizen_line.rpy 有 22 处 image 路径磁盘上不存在
endings_gallery_screen_shows_unlocked_ending断言口径注册表里该结局名是「著书存道」,断言写的是「郎中 · 著书存道」
doctor_line_forced_death_when_vitality_zero实现体力归零、结局被强制改写成「病中殉职」后,doctor_complete 没有置位,advance until eval doctor_complete 超时

伪红(由百姓线 suite 的状态污染产生,共 8 条):郎中线的 entry_from_main_menu、observation_to_e_book、heal_to_e_heal_fame、flee_to_e_hermit、state_reset_on_reentry,以及 citizen_line_ending_registered_in_gallery。这 8 条在单独跑对应 suite 时全部通过。

百姓线自身的 3 条:citizen_line_entry_from_main_menu 与 citizen_line_hold_to_ending 是断言口径问题——用例把「留在家里,把门闩死,陪着发热的妹妹。」当成了进入百姓线后的第一个菜单项,实际它是第二个菜单的第一项,断言自然落空;第三条是被自己前一条污染出来的。

这 8 条伪红不能记进产品缺陷台账。 把测试自身的缺陷算到产品头上,后面所有基于这份报告的判断都会跟着错。

6.3 Lint

feature/doctor-line 上 lint 报 24 条问题、退出码 1:22 条是 citizen_line.rpy 的 image 路径不存在,2 条是 config.name / config.version 重复定义。master 上是 3 条红,套件本身是全绿的。

值得注意的是:lint 报了 22 处资源缺失,却没报 prop_boiled_cloth.jpg 扩展名与内容不符——lint 的检查项和运行时的判据不是同一套。

6.4 CI 侧

流水线「测试」(id 699f49d8)已用 push 与 MR 事件触发。master 上的 CI #33 是 61 | 5 passed | 56 xfailed | 0 failed,Status: PASSED——master 上三条线都还没实现,56 条用例全部标成 xfailed,一条误报都没有,说明 xfail 守卫的设计是对的。CI #37(2026-10-29 18:39 → 18:48)状态为 FAILED,门禁红。

流水线定义住两份:master 上是权威副本,codearts-pipeline 上是同步副本,两份必须逐字节一致。MR 事件只认目标分支上的 YAML,所以队友往 master 提 MR 前,master 上必须已经有那份文件——这条也是实测踩出来的(连续三次 MR 事件零运行)。

6.5 项目评述

当前证据能够支持的结论:

  • 三条线的测试套件都能接进 CI,56 条 xfailed 无一误报,分支适配是可靠的。
  • 郎中线引用的 21 个美术资源里 20 个在磁盘上可用。
  • 结局注册表的一致性检查有效,已经把 e_epidemic_death 这个「注了册却永远解锁不了」的结局钉住了。
  • 郎中线的观察线、施救线、逃亡线三条支线,在单独执行时都能完整走通并把结局登记进图鉴。

当前证据不能支持的结论:

  • 本轮结果不能代表项目已经通过全面的性能、兼容性或长期稳定性验收。
  • 那 8 条伪红对应的产品行为到底有没有问题,尚未判定——要先修好套件的独立性再重测。
  • 百姓线整条线在当前分支缺 22 处美术,这条线能不能真正玩通,还没有验证过。

后续优先级:

  1. 先修套件的跨 suite 独立性(每个 suite 独立进程,或每个 suite 前强制 full restart 并自检状态),否则红绿不可信——这是测试自身的第一优先级。
  2. 补 e_epidemic_death 的注册与 unlock_ending 调用。
  3. 修 prop_boiled_cloth 的真实格式(或把扩展名改对)。
  4. 补百姓线的 22 处美术。
  5. 重新校准两条断言口径(百姓线首菜单、图鉴标题)。
  6. 修正强制病亡分支里 doctor_complete 的置位。

七 测试体会

  1. Lint 全绿 ≠ 游戏能玩;但 Lint 红了也别当成「只有这些坏了」。 本轮 lint 报 22 处资源缺失,而运行时还坏着一张扩展名与内容不符的 PNG,lint 一句没提。两道门的检查项不是同一套判据。

  2. 一个失败用例后面跟着一串超时,先怀疑测试自己。 教材 2.1.3 第(5)条写得很直白:单元测试的运行、通过、失败不依赖于别的测试。本轮整套连跑 14 红,拆开单跑只剩 6 红,8 条红是套件自己的状态污染造出来的。

  3. AI 给的根因必须拿已知错例反测。 13.4.3 说与 AI 协作要「要求解释、保持批判」。本轮 AI 给了三个看起来非常合理的根因,用探针逐个反测后全部推翻。AI 生成的断言如果只是复述实现,还会把实现里的错误一起复制进测试——最后一条判据仍是:结论以实际运行记录为准。

  4. 「不适用」与「失败」要分开记,「没跑到」与「通过」也要分开。 九条 xfailed 一条误报都没有,说明守卫设计对了;反过来,引擎中途崩掉时报告器仍会报 PASSED,所以我们自己解析汇总,把 not run 也算成失败。

  5. 自动化的价值不在「跑得快」,在「能重复」。 同一套件今天跑出 38/61,明天还得跑出 38/61,这次的结论才有意义。保留真实失败、说明阻塞范围,比笼统的「全部通过」更能帮助团队判断下一步该解决什么。

...全文

相关推荐

科技中介服务机构如何提升技术评估的智能化与专业化水平?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

BIZKEEN

高校技术转移办公室人员在开展技术推介时,如何高效生成科技成果展示材料?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

BIZKEEN

产业园区运营负责人如何实现创新资源的高效整合与精准配置?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

BIZKEEN

科技中介服务机构如何利用平台提升服务质量与运营效率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

BIZKEEN

OxygenXMLEditor-v26.x + keygen

OxygenXMLEditor-v26.x + keygen

fabushi_
评论
收藏 已收藏
分享
返回顶部