ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Zulip 自动化测试体系完全指南:从 test-all 到单测隔离策略

Zulip 自动化测试体系完全指南:从 test-all 到单测隔离策略 Zulip 自动化测试体系完全指南从 test-all 到单测隔离策略【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulipZulip 是少数把测试套件当作第一等基础设施来建设的开源项目它的后端测试套件test-backend可以在约一分钟内跑完约 98% 行覆盖率核心代码 100%的完整测试前端 Node 单测套件在 10 秒内完成。本文以 docs/testing/testing.md 为骨架结合仓库内测试工具链源码系统讲解 Zulip 测试体系的分层结构、每个套件的运行方式与核心参数、以及测试套件禁止访问外网这一关键设计策略帮助你在本地开发环境中快速定位并运行与改动相关的测试子集。测试体系概览为什么 Zulip 敢于快速前进而不破坏功能Zulip 的工程策略可以概括为move quickly without breaking things快速前进而不破坏功能。支撑这一策略的正是多年系统化投入构建的测试、工具链与开发规范。在 docs/testing/philosophy.md 中项目明确记录了这一思路测试套件的性能与可靠性是维护者视为优先级的核心资产。当套件变慢或变得不确定flaky时开发者会本能地回避运行测试进而回避改进测试最终整个代码库的测试质量会螺旋式恶化——这是大型软件项目常见的测试腐烂test rot命运Zulip 的目标正是避免它。因此项目制定了两个硬性性能指标完整后端套件test-backend的目标运行时间约 1 分钟完整前端套件test-js-with-node的目标运行时间10 秒以内。与之配套的工程手段包括测试绝不访问外网详见下文网络隔离策略通过唯一随机前缀隔离不同测试对 Redis、memcached 的键污染每个测试用例运行在数据库事务中测试结束后回滚事务每个测试进程只与一份专用的模板测试数据库交互进程结束即销毁。对于任何非确定性失败的测试Zulip 会像对待产品级 bug 一样严肃调查。运行测试从全量 test-all 到秒级单测全量运行./tools/test-allZulip 的所有测试都必须运行在 Zulip 开发环境内若使用 Vagrant 需先vagrant ssh进入。运行全部套件与 CI 的覆盖范围相当只需./tools/test-all从 tools/test-all 的源码可以看到它会依序执行后端组与前端组的十余个检查项先是./tools/check-provision预检环境然后运行后端 lint、test-tools、带--include-webhooks --ban-console-output的完整test-backend、test-migrations、帮助中心文档检查、API 文档实测、uv lock --check依赖锁定校验前端部分则运行前端 lint、test-js-with-node、schema 校验、i18n 与大小写规范检查、Puppeteer 端到端测试等optimize-svg、test-run-dev、test-queue-worker-reload等低频检查被注释为低变更频率而未纳入本地全量。工具自身也在输出中明确提醒test-all很慢推荐只运行相关的子套件依赖 CI 跑完整套件以获得快速的编辑-测试循环。开发时的秒级迭代活跃开发时编辑/刷新循环通常只用下面这些秒级命令./tools/lint zerver/models/__init__.py # 只 lint 你刚改动的文件 ./tools/test-backend zerver.tests.test_markdown.MarkdownEmbedsTest.test_inline_youtube ./tools/test-backend MarkdownEmbedsTest # 更多选项见 test-backend --help ./tools/test-js-with-node util每个工具都提供--help查看完整选项。例如 tools/test-backend 支持按点分路径dotted.test.name精确定位单个测试类或方法也支持只传类名./tools/test-backend --help是学习这些选项的最佳途径。四大主要测试套件Zulip 有四大主要测试套件每个开发者都会接触各自有独立文档套件命令测试对象详情文档Linters十余个并行 lint 器./tools/lintPython/JS/TS/模板/CSS/JSON/Markdown/Puppet/shell 等docs/testing/linters.mdDjango 后端测试./tools/test-backend服务端 Python 代码基于django.testdocs/testing/testing-with-django.mdNode 前端单测./tools/test-js-with-node经 node.js 运行的 JavaScript/TypeScript 前端逻辑docs/testing/testing-with-node.mdPuppeteer 端到端测试./tools/test-js-with-puppeteer通过真实 Chromium 浏览器驱动的黑盒 UI 测试docs/testing/testing-with-puppeteer.mdLinters./tools/lint./tools/test-all会自动运行 lint也可单独运行或只检查指定文件./tools/lint ./tools/lint web/src/compose.ts ./tools/lint web/src/./tools/lint内部通过 fork 子进程并行执行多项检查。常用选项包括--fix让支持自动修复的 linter 直接修掉基础问题、--verbose给出--fix覆盖不到的常见错误的修复指引、--skip/--only只运行部分 linter、-m只检查已修改文件。项目还提供 Git pre-commit 钩子见 docs/git/zulip-tools.md提交时几百毫秒内自动对改动文件跑tools/lint。注意linter 只检查 Git 已跟踪的文件新增文件需先git add。lint 检查覆盖的范围相当广Python 用 Ruff配置在pyproject.toml的[tool.ruff]段见 pyproject.tomlJS/TS 用 ESLintCSS/JS/TS/YAML 格式用 PrettierPuppet 清单用官方 validatorDjango/Handlebars 模板有自研的标签配对与缩进校验驱动脚本为 tools/check-templates引擎为 tools/lib/template_parser.pyPython 静态类型用 mypy、TS 编译用 tsc此外还有针对行尾空白、tab 字符、Python 行长的通用检查以及大量自研的正则规则集中于 tools/linter_lib。特殊的#ignorelinelength注释可豁免超长行例如注释中极长的 URL。Django 后端测试./tools/test-backend后端测试全部位于zerver/tests/目录基于 Django 的django.test框架在开发环境内多线程并行运行。迭代模式下按点分约定运行单个测试或模块./tools/test-backend zerver.tests.test_queue_worker.WorkerTest常用选项包括--verbose详细输出-h可发现覆盖率、URL 覆盖率、慢测试等辅助功能--profile对测试做性能剖析--stop或-x在第一个错误后停止--rerun只重跑上次失败的测试--coverage生成覆盖率 HTML 报告工具会打印开发环境内可访问的报告 URL报告还会显示每行被哪些测试执行便于反查已有测试且test-backend --coverage会断言一批指定文件必须保持 100% 覆盖率否则报错--ban-console-output检查是否存在杂散print规范的测试不应向控制台输出任何内容--include-webhooks默认不带参数的test-backend会跳过zerver/webhooks/下的 webhook 集成测试它们约占全部后端测试 25% 的运行时间CI 始终用--include-webhooks全量运行本地改 webhook 时建议直接test-backend zerver/webhooks。编写测试时核心基类是 zerver/lib/test_classes.py 中的ZulipTestCase以及 webhook 专用的WebhookTestCase辅助函数集中在 zerver/lib/test_helpers.py。所有测试共享同一份莎士比亚角色命名的 fixture 用户数据隶属 zulip.com realm生成逻辑见 tools/setup/generate-fixtures测试内的数据库写入发生在事务中、结束后回滚。常用测试策略包括针对 URL 端点的 endpoint 测试self.login()client_get/client_postassert_json_success/assert_json_error组合、库函数单测、JSON fixture 驱动的第三方集成测试fixtures 存于zerver/tests/fixtures、用queries_captured()/assert_database_query_count防止 N1 查询的 SQL 性能测试、验证事件系统数据一致性的BaseAction.do_test()、以及验证未授权访问被正确拒绝的 negative tests。模板渲染健康检查见 zerver/tests/test_templates.py。Node 前端单测./tools/test-js-with-node这是 Zulip 测试 JS/TS 前端代码的首选方式比 Puppeteer 黑盒测试快 100 倍以上且更容易写对。测试文件位于web/tests/如web/tests/stream_data.test.cjs测试名与web/src下的被测模块一一对应。运行tools/test-js-with-node tools/test-js-with-node --coverage # 生成覆盖率报告与 Puppeteer 不同Node 单测没有浏览器、不连接服务器、通常也不使用完整虚拟 DOM仅少量测试用jsdom。DOM 操作Zulip 中几乎全部经 jQuery 完成通过自研的约 500 行库 web/tests/lib/zjquery.cjs 进行stub——它提供大部分 jQuery DOM 操作的最小实现例如$obj.set_find_results(selector, $value)可让$obj.find(selector)返回预设值遇到未 stub 的调用会报Error: You must create a stub for $(#foo).bar。它的单测文件 web/tests/zjquery.test.cjs 同时充当使用文档。依赖处理上测试文件会显式声明模块zrequire(util)载入真实模块代码mock_esm(../../web/src/reminder, {...})整体 stub 掉某个模块也可以先zrequire(narrow_state)再在测试内覆盖个别函数实现借用真实功能 局部 stub的混合模式。测试运行器 web/tests/lib/index.cjs 会自动运行web/tests下所有.test.cjs文件因此新建测试只需在该目录新增文件。此外mock_template工具用于校验 Handlebars 模板位于web/templates被调用时传入的 context 数据是否正确可按需选择是否真实渲染模板。覆盖率报告中完全未被测试的模块不列出因此整体数字偏乐观但单文件数据是准确的分支覆盖率达到 80% 是合理目标。Puppeteer 端到端测试./tools/test-js-with-puppeteer部分代码如登录导航、剪贴板、键盘快捷键等浏览器行为交互必须用真实浏览器验证tools/test-js-with-puppeteer测试文件位于web/e2e-tests/公共辅助函数在 web/e2e-tests/lib/common.ts。它们连接真实 Chromium 浏览器与真实 Zulip 开发服务器服务器使用zproject/test_extra_settings.py中的隔离数据库配置是典型的黑盒测试——步骤就是用户会做的操作按下这个键等待这个元素出现点击这个元素。例如测试x快捷键打开私信撰写框async function test_private_message_compose_shortcut(page) { await page.keyboard.press(KeyX); await page.waitForSelector(#private_message_recipient, {visible: true}); await common.pm_recipient.expect(page, ); await close_compose_box(page); }waitForSelector是这类测试的命门缺失或等待对象错误的 wait 会造成非确定性失败且必须使用{visible: true}选项否则元素只要存在于 DOM 就停止等待对常驻 DOM、用显示/隐藏切换的 UI 完全无效。调试工具包括断言失败时自动生成var/puppeteer/*.png截图、console.log打印调试、以及在慢速 CI 机器上本地能过、CI 必挂时优先怀疑 wait 缺失。写完新测试后项目要求循环运行 100 次以确认无 flaky 风险。由于黑盒测试慢、维护成本高且易产生非确定性失败Zulip 的策略是两者皆可时优先写 Node 单测。其余十余个小套件各有其不可替代的价值除四大主套件外Zulip 还有约十几个小型测试套件。它们大多因为性能或需要动环境的原因没有并入主套件但各自守着一类特殊 bug./tools/test-migrations校验zerver/migrations中的数据库迁移内容与zerver/models/*.py定义的模型一致保证迁移不会漏改模型参见 docs/subsystems/schema-migrations.md./tools/test-documentation检查 ReadTheDocs 站点文档中的断链./tools/test-help-documentation检查/help/帮助中心文档及相关页面的断链./tools/test-api实测/api的 API 文档真的能用其代码定义在zerver/openapi/python_examples.py./tools/check-capitalization检查所有面向用户的翻译字符串是否符合 Zulip 的大小写规范依赖tools/lib/capitalization.py中的专有名词豁免清单IGNORED_PHRASES./tools/check-frontend-i18n检查 Handlebars 模板中一个常见 bug——翻译含变量的代码块时用错了语法./tools/test-run-dev验证run-dev能正常启动防止开发环境被改坏./tools/test-queue-worker-reload验证 Zulip 的队列处理器在代码变更后能正确重载./tools/setup/optimize-svg检查所有集成 logo 的 SVG 图形是否已优化压缩第三方 logo 不便修改此检查防止代码库无限膨胀./tools/test-tools为开发工具链主要是各类 linter编写的自动化测试它们不用于生产环境。网络隔离策略测试套件绝不访问外网这是 Zulip 测试体系中最具特色的设计决策也是原文档重点阐述的部分。作为政策Zulip 的测试套件绝不允许发起任何外出的 HTTP 或其他网络请求原因有二会发外网请求的测试在用户离线时会失败更隐蔽的是这类测试隐式依赖第三方服务的可用性——一旦对方临时故障测试就会非确定性地失败白白消耗大量开发者时间。因此测试中对任何 Zulip 代码路径可能发出的外网请求一律用mock把响应硬编码进测试。实现上并不复杂使用测试 fixture即测试用的固定数据配合 Python 的mock.patch函数为每一个外发请求指定应有的 HTTP 响应。Zulip 还大量使用responses库来 mock 掉requests调用可用git grep responses.add找到示例具体手法见 docs/testing/testing-with-django.md 的 Zulip mocking practices 一节低层HttpRequest场景则用 Zulip 自有的HostRequestMock类。这套政策在后端主套件中是被强制执行的tools/test-backend 通过覆盖外出 HTTP 代码路径常用的库函数httplib2.Http().request、requests.request等让它们直接抛异常。从源码看其实现是block_internet()上下文管理器tools/test-backend它把responses.ConnectionError替换为自定义的ZulipInternetBlockedError并挂上responses.RequestsMock()整个测试运行期间任何未注册的 URL 访问都会触发该异常同时脚本还会移除http_proxy/https_proxy环境变量杜绝代理绕行。这种强制虽不穷尽Python 中还有很多其他方式触网但足以拦截绝大多数新代码依赖外网的常见情况报错形如File tools/test-backend, line 120, in internet_guard raise Exception(Outgoing network requests are not allowed in the Zulip tests. Exception: Outgoing network requests are not allowed in the Zulip tests. ...唯一的例外文档测试文档类测试是上述政策的唯一例外——要验证文档链接是否有效除了真的去访问它们别无他法因此这类测试确实会偶发非确定性失败。项目采取的折中方案是在 CI 中文档测试只验证 Zulip 项目自己控制的网站zulip.com、Zulip 的 GitHub、ReadTheDocs不去验证第三方网站链接。这从 tools/test-all 中也可印证——本地全量运行使用--skip-external-links跳过外部链接检查。手动测试与端到端哲学自动化覆盖不了的部分如 CSS 视觉效果、Markdown 特殊排版Zulip 依赖手动测试manual QA兜底。手动测试的建议见 docs/testing/manual-testing.md优先抓安全/权限类 bug用隐身窗口模拟多用户始终开着 inspector 控制台观察告警习惯用 Cordelia 作主用户、Hamlet 作对话对象、Iago 测管理员功能。与本文主题相关的开发环境使用细节见 docs/development/using.md。在测试编写哲学上详见 docs/testing/philosophy.mdZulip 反对为内部小函数堆砌大量纯单测——那会让重构背上沉重的测试修改成本。更优的做法是针对你承诺保持稳定的接口写测试Zulip 的 Web 应用、移动端与第三方客户端共用同一套 API因此绝大多数服务端测试直接打 Zulip 端到端 APIwebhook 测试则用从真实第三方服务抓取的 payload 打向 webhook 端点并断言输出消息。同时有安全影响的代码绝不重复实现——像access_stream_by_id这类权限校验函数被精心编写并集中测试所有可能向用户泄露数据的代码路径都必须经由这些函数取数每个条件独立成行以便覆盖率工具验证这是把安全逻辑 bug系统性消灭在结构层面的关键手段。与 CI 的衔接.github/workflows/zulip-ci.yml 是主 CI 工作流在全部受支持平台上运行各主套件其中 Puppeteer 与文档测试只在单个平台运行它们慢且与基座 OS/Python 版本关联度低production-suite.yml则构建发布 tarball 并在全新容器中安装后跑 Nagios 等检查。tools/ci下的代码是测试套件与 CI 之间的薄封装。CI 同样坚持本地秒级可复现原则——任何 CI 行为都应有本地 1 分钟内最好 3 秒内的运行方式详见 docs/testing/continuous-integration.md。快速上手指南给初次接触 Zulip 测试体系的开发者一份速查清单只改了一个 Python 文件跑./tools/lint 文件加./tools/test-backend指定测试改了前端 JS/TS 逻辑跑./tools/test-js-with-node可--coverage动了浏览器交互或 UI 流程跑./tools/test-js-with-puppeteer所有测试都遵守无外网铁律网络相关场景一律用mock.patch/responses造 fixture提交前用 Git pre-commit 钩子自动 lint最终交给 CI 全量把关docs/testing/continuous-integration.md。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表